diff --git a/build.gradle b/build.gradle index 2416a7aedf..7eb33121c9 100644 --- a/build.gradle +++ b/build.gradle @@ -86,7 +86,7 @@ task ciBuild { wrapper { - gradleVersion = '4.10.2' + gradleVersion = '4.10.3' distributionType = Wrapper.DistributionType.ALL } diff --git a/databases/hana/matrix.gradle b/databases/hana/matrix.gradle index 8782b540d7..59d7bc269e 100644 --- a/databases/hana/matrix.gradle +++ b/databases/hana/matrix.gradle @@ -5,4 +5,4 @@ * See the lgpl.txt file in the root directory or . */ -jdbcDependency 'com.sap.cloud.db.jdbc:ngdbc:2.2.16' \ No newline at end of file +jdbcDependency 'com.sap.cloud.db.jdbc:ngdbc:2.4.59' \ No newline at end of file diff --git a/databases/mariadb/matrix.gradle b/databases/mariadb/matrix.gradle index 7ebdee1458..39b03e208f 100644 --- a/databases/mariadb/matrix.gradle +++ b/databases/mariadb/matrix.gradle @@ -4,4 +4,4 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later. * See the lgpl.txt file in the root directory or . */ -jdbcDependency 'org.mariadb.jdbc:mariadb-java-client:1.5.7' \ No newline at end of file +jdbcDependency 'org.mariadb.jdbc:mariadb-java-client:2.2.4' \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/jdbc/Database_Access.adoc b/documentation/src/main/asciidoc/userguide/chapters/jdbc/Database_Access.adoc index 049e8e280f..190c9b23e5 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/jdbc/Database_Access.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/jdbc/Database_Access.adoc @@ -39,6 +39,14 @@ The `DataSource` `ConnectionProvider` also (optionally) accepts the `hibernate.c If specified, the https://docs.oracle.com/javase/8/docs/api/javax/sql/DataSource.html#getConnection-java.lang.String-java.lang.String-[`DataSource#getConnection(String username, String password)`] will be used. Otherwise, the no-arg form is used. +[[database-connectionprovider-driver]] +=== Driver Configuration +`hibernate.connection.driver_class`:: The name of the JDBC Driver class to use +`hibernate.connection.url`:: The JDBC connection url +`hibernate.connection.*`:: All such setting names (except the <>) will have the `hibernate.connection.` prefix stripped. The remaining name and the original value will be passed to the driver as a JDBC connection property + +NOTE: Not all properties apply to all situations. For example, if you are providing a data source, `hibernate.connection.driver_class` setting will not be used. + [[database-connectionprovider-c3p0]] === Using c3p0 @@ -48,13 +56,10 @@ To use the c3p0 integration, the application must include the `hibernate-c3p0` m ==== Hibernate also provides support for applications to use http://www.mchange.com/projects/c3p0/[c3p0] connection pooling. -When using this c3p0 support, a number of additional configuration settings are recognized. +When c3p0 support is enabled, a number of c3p0-specific configuration settings are recognized in addition to the general ones described in <>. Transaction isolation of the Connections is managed by the `ConnectionProvider` itself. See <>. -`hibernate.connection.driver_class`:: The name of the JDBC Driver class to use -`hibernate.connection.url`:: The JDBC connection url. -Any settings prefixed with `hibernate.connection.` (other than the "special ones"):: These all have the `hibernate.connection.` prefix stripped and the rest will be passed as JDBC connection properties `hibernate.c3p0.min_size` or `c3p0.minPoolSize`:: The minimum size of the c3p0 pool. See http://www.mchange.com/projects/c3p0/#minPoolSize[c3p0 minPoolSize] `hibernate.c3p0.max_size` or `c3p0.maxPoolSize`:: The maximum size of the c3p0 pool. See http://www.mchange.com/projects/c3p0/#maxPoolSize[c3p0 maxPoolSize] `hibernate.c3p0.timeout` or `c3p0.maxIdleTime`:: The Connection idle time. See http://www.mchange.com/projects/c3p0/#maxIdleTime[c3p0 maxIdleTime] diff --git a/gradle/libraries.gradle b/gradle/libraries.gradle index 4fbbdce617..2b0a94227e 100644 --- a/gradle/libraries.gradle +++ b/gradle/libraries.gradle @@ -28,11 +28,11 @@ ext { geolatteVersion = '1.4.0' // Wildfly version targeted by module ZIP; Arquillian/Shrinkwrap versions used for CDI testing and testing the module ZIP - wildflyVersion = '14.0.1.Final' - arquillianVersion = '1.4.0.Final' + wildflyVersion = '17.0.1.Final' + arquillianVersion = '1.4.1.Final' shrinkwrapVersion = '1.2.6' shrinkwrapDescriptorsVersion = '2.0.0' - wildflyArquillianContainerVersion = '2.1.1.Final' + wildflyArquillianContainerVersion = '2.2.0.Final' jodaTimeVersion = '2.3' @@ -109,8 +109,7 @@ ext { hsqldb: "org.hsqldb:hsqldb:2.3.2", derby: "org.apache.derby:derby:10.11.1.1", postgresql: 'org.postgresql:postgresql:42.2.2', - //Upgrade MySQL Driver only when this issue gets fixed: https://bugs.mysql.com/bug.php?id=85941 - mysql: 'mysql:mysql-connector-java:5.1.46', + mysql: 'mysql:mysql-connector-java:8.0.17', mariadb: 'org.mariadb.jdbc:mariadb-java-client:2.2.3', oracle: 'com.oracle.jdbc:ojdbc8:12.2.0.1', @@ -159,7 +158,7 @@ ext { wildfly_arquillian_container_managed: "org.wildfly.arquillian:wildfly-arquillian-container-managed:${wildflyArquillianContainerVersion}", jboss_vfs: "org.jboss:jboss-vfs:3.2.11.Final", jipijapa_spi: "org.wildfly:jipijapa-spi:${wildflyVersion}", - wildfly_transaction_client : 'org.wildfly.transaction:wildfly-transaction-client:1.0.3.Final', + wildfly_transaction_client : 'org.wildfly.transaction:wildfly-transaction-client:1.1.7.Final', jboss_ejb_spec_jar : 'org.jboss.spec.javax.ejb:jboss-ejb-api_3.2_spec:1.0.0.Final', jboss_annotation_spec_jar : 'org.jboss.spec.javax.annotation:jboss-annotations-api_1.2_spec:1.0.0.Final' diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 28861d273a..94336fcae9 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d76b502e22..ae45383b6d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.3-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java index 789d01c8a5..52cf2f8ce5 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java @@ -99,6 +99,7 @@ import static org.hibernate.cfg.AvailableSettings.LOG_SESSION_METRICS; import static org.hibernate.cfg.AvailableSettings.MAX_FETCH_DEPTH; import static org.hibernate.cfg.AvailableSettings.MULTI_TENANT_IDENTIFIER_RESOLVER; import static org.hibernate.cfg.AvailableSettings.NATIVE_EXCEPTION_HANDLING_51_COMPLIANCE; +import static org.hibernate.cfg.AvailableSettings.OMIT_JOIN_OF_SUPERCLASS_TABLES; import static org.hibernate.cfg.AvailableSettings.ORDER_INSERTS; import static org.hibernate.cfg.AvailableSettings.JPA_CALLBACKS_ENABLED; import static org.hibernate.cfg.AvailableSettings.ORDER_UPDATES; @@ -212,6 +213,7 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions { private final boolean procedureParameterNullPassingEnabled; private final boolean collectionJoinSubqueryRewriteEnabled; private boolean jdbcStyleParamsZeroBased; + private final boolean omitJoinOfSuperclassTablesEnabled; // Caching private boolean secondLevelCacheEnabled; @@ -360,6 +362,7 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions { CONVENTIONAL_JAVA_CONSTANTS, BOOLEAN, true ); this.procedureParameterNullPassingEnabled = cfgService.getSetting( PROCEDURE_NULL_PARAM_PASSING, BOOLEAN, false ); this.collectionJoinSubqueryRewriteEnabled = cfgService.getSetting( COLLECTION_JOIN_SUBQUERY, BOOLEAN, true ); + this.omitJoinOfSuperclassTablesEnabled = cfgService.getSetting( OMIT_JOIN_OF_SUPERCLASS_TABLES, BOOLEAN, true ); final RegionFactory regionFactory = serviceRegistry.getService( RegionFactory.class ); if ( !NoCachingRegionFactory.class.isInstance( regionFactory ) ) { @@ -1064,6 +1067,12 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions { return enhancementAsProxyEnabled; } + @Override + public boolean isOmitJoinOfSuperclassTablesEnabled() { + return omitJoinOfSuperclassTablesEnabled; + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // In-flight mutation access diff --git a/hibernate-core/src/main/java/org/hibernate/boot/registry/classloading/internal/AggregatedServiceLoader.java b/hibernate-core/src/main/java/org/hibernate/boot/registry/classloading/internal/AggregatedServiceLoader.java index eead6d561a..6d902d046d 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/registry/classloading/internal/AggregatedServiceLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/registry/classloading/internal/AggregatedServiceLoader.java @@ -15,12 +15,15 @@ import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; +import java.util.ServiceConfigurationError; import java.util.ServiceLoader; import java.util.Set; import java.util.function.Supplier; import java.util.stream.Stream; import org.hibernate.AssertionFailure; +import org.hibernate.internal.CoreLogging; +import org.hibernate.internal.CoreMessageLogger; /** * A service loader bound to an {@link AggregatedClassLoader}. @@ -28,6 +31,8 @@ import org.hibernate.AssertionFailure; */ abstract class AggregatedServiceLoader { + private static final CoreMessageLogger log = CoreLogging.messageLogger( AggregatedServiceLoader.class ); + private static final Method SERVICE_LOADER_STREAM_METHOD; private static final Method PROVIDER_TYPE_METHOD; @@ -145,17 +150,16 @@ abstract class AggregatedServiceLoader { * @param The type of loaded services. */ private static class ClassPathAndModulePathAggregatedServiceLoader extends AggregatedServiceLoader { + private final Class serviceContract; + private final ServiceLoader aggregatedClassLoaderServiceLoader; private final List> delegates; private Collection cache = null; private ClassPathAndModulePathAggregatedServiceLoader(AggregatedClassLoader aggregatedClassLoader, Class serviceContract) { + this.serviceContract = serviceContract; this.delegates = new ArrayList<>(); - // Always try the aggregated class loader first - this.delegates.add( ServiceLoader.load( serviceContract, aggregatedClassLoader ) ); - - // Then also try the individual class loaders, - // because only them can instantiate services provided by jars in the module path + this.aggregatedClassLoaderServiceLoader = ServiceLoader.load( serviceContract, aggregatedClassLoader ); final Iterator clIterator = aggregatedClassLoader.newClassLoaderIterator(); while ( clIterator.hasNext() ) { this.delegates.add( @@ -185,54 +189,94 @@ abstract class AggregatedServiceLoader { return cache; } - @SuppressWarnings("unchecked") private Collection loadAll() { Set alreadyEncountered = new HashSet<>(); Set result = new LinkedHashSet<>(); - delegates.stream() - // Each loader's stream() method returns a stream of service providers: flatten these into a single stream - .flatMap( delegate -> { - try { - return (Stream>) SERVICE_LOADER_STREAM_METHOD.invoke( delegate ); - } - catch (RuntimeException | IllegalAccessException | InvocationTargetException e) { - throw new AssertionFailure( "Error calling ServiceLoader.stream()", e ); - } - } ) - // For each provider, check its type to be sure we don't use a provider twice, then get the service - .forEach( provider -> { - Class type; - try { - type = (Class) PROVIDER_TYPE_METHOD.invoke( provider ); - } - catch (RuntimeException | IllegalAccessException | InvocationTargetException e) { - throw new AssertionFailure( "Error calling ServiceLoader.Provider.type()", e ); - } - String typeName = type.getName(); - /* - * We may encounter the same service provider multiple times, - * because the individual class loaders may give access to the same types - * (at the very least a single class loader may be present twice in the aggregated class loader). - * However, we only want to get the service from each provider once. - * - * ServiceLoader.stream() is useful in that regard, - * since it allows us to check the type of the service provider - * before the service is even instantiated. - * - * We could just instantiate every service and check their type afterwards, - * but 1. it would lead to unnecessary instantiation which could have side effects, - * in particular regarding class loading, - * and 2. the type of the provider may not always be the type of the service, - * and one provider may return different types of services - * depending on conditions known only to itself. - */ - if ( alreadyEncountered.add( typeName ) ) { - result.add( provider.get() ); - } - } ); + + // Always try the aggregated class loader first + Iterator> providerIterator = providerStream( aggregatedClassLoaderServiceLoader ) + .iterator(); + while ( providerIterator.hasNext() ) { + Supplier provider = providerIterator.next(); + collectServiceIfNotDuplicate( result, alreadyEncountered, provider ); + } + + /* + * Then also try the individual class loaders, + * because only them can instantiate services provided by jars in the module path. + */ + for ( ServiceLoader delegate : delegates ) { + providerIterator = providerStream( delegate ).iterator(); + /* + * Note that advancing the stream itself can lead to (arguably) "legitimate" errors, + * where we fail to load the service, + * but only because individual classloader has its own definition of the service contract class, + * which is different from ours. + * In that case (still arguably), the error should be ignored. + * That's why we wrap the call to hasNext in a method that catches an logs errors. + * See https://hibernate.atlassian.net/browse/HHH-13551. + */ + while ( hasNextIgnoringServiceConfigurationError( providerIterator ) ) { + Supplier provider = providerIterator.next(); + collectServiceIfNotDuplicate( result, alreadyEncountered, provider ); + } + } + return result; } + @SuppressWarnings("unchecked") + private Stream> providerStream(ServiceLoader serviceLoader) { + try { + return ( (Stream>) SERVICE_LOADER_STREAM_METHOD.invoke( serviceLoader ) ); + } + catch (RuntimeException | IllegalAccessException | InvocationTargetException e) { + throw new AssertionFailure( "Error calling ServiceLoader.stream()", e ); + } + } + + private boolean hasNextIgnoringServiceConfigurationError(Iterator iterator) { + while ( true ) { + try { + return iterator.hasNext(); + } + catch (ServiceConfigurationError e) { + log.ignoringServiceConfigurationError( serviceContract, e ); + } + } + } + + /* + * We may encounter the same service provider multiple times, + * because the individual class loaders may give access to the same types + * (at the very least a single class loader may be present twice in the aggregated class loader). + * However, we only want to get the service from each provider once. + * + * ServiceLoader.stream() is useful in that regard, + * since it allows us to check the type of the service provider + * before the service is even instantiated. + * + * We could just instantiate every service and check their type afterwards, + * but 1. it would lead to unnecessary instantiation which could have side effects, + * in particular regarding class loading, + * and 2. the type of the provider may not always be the type of the service, + * and one provider may return different types of services + * depending on conditions known only to itself. + */ + private void collectServiceIfNotDuplicate(Set result, Set alreadyEncountered, Supplier provider) { + Class type; + try { + type = (Class) PROVIDER_TYPE_METHOD.invoke( provider ); + } + catch (RuntimeException | IllegalAccessException | InvocationTargetException e) { + throw new AssertionFailure( "Error calling ServiceLoader.Provider.type()", e ); + } + String typeName = type.getName(); + if ( alreadyEncountered.add( typeName ) ) { + result.add( provider.get() ); + } + } + @Override public void close() { cache = null; diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java index 1df0a779ae..a3de3a7963 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java @@ -447,4 +447,9 @@ public class AbstractDelegatingSessionFactoryOptions implements SessionFactoryOp public boolean isEnhancementAsProxyEnabled() { return delegate.isEnhancementAsProxyEnabled(); } + + @Override + public boolean isOmitJoinOfSuperclassTablesEnabled() { + return delegate.isOmitJoinOfSuperclassTablesEnabled(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java index 044ab8c94c..be055237b3 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java @@ -314,4 +314,6 @@ public interface SessionFactoryOptions { default boolean isEnhancementAsProxyEnabled() { return false; } + + boolean isOmitJoinOfSuperclassTablesEnabled(); } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/XmlMappingBinderAccess.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/XmlMappingBinderAccess.java index f2d7faf89e..8102865f2a 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/XmlMappingBinderAccess.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/XmlMappingBinderAccess.java @@ -83,13 +83,13 @@ public class XmlMappingBinderAccess { xmlInputStream.close(); } catch (IOException e) { - LOG.debugf( "Unable to close InputStream obtained from InputStreamAccess : " + xmlInputStreamAccess.getStreamName() ); + LOG.debugf( "Unable to close InputStream obtained from InputStreamAccess : %s", xmlInputStreamAccess.getStreamName() ); } } } public Binding bind(InputStream xmlInputStream) { - LOG.tracef( "reading mappings from InputStream" ); + LOG.trace( "reading mappings from InputStream" ); final Origin origin = new Origin( SourceType.INPUT_STREAM, null ); return new InputStreamXmlSource( origin, xmlInputStream, false ).doBind( getMappingBinder() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/CodeTemplates.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/CodeTemplates.java index da3fdc33a7..2c870b1844 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/CodeTemplates.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/CodeTemplates.java @@ -20,10 +20,10 @@ import org.hibernate.bytecode.enhance.internal.tracker.SimpleFieldTracker; import org.hibernate.bytecode.enhance.spi.CollectionTracker; import org.hibernate.bytecode.enhance.spi.EnhancerConstants; import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; -import org.hibernate.engine.spi.ExtendedSelfDirtinessTracker; -import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.CompositeOwner; import org.hibernate.engine.spi.CompositeTracker; +import org.hibernate.engine.spi.ExtendedSelfDirtinessTracker; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; import net.bytebuddy.asm.Advice; @@ -358,9 +358,9 @@ class CodeTemplates { static void enter(@FieldValue Collection field, @Advice.Argument(0) Collection argument, @MappedBy String mappedBy) { if ( field != null && Hibernate.isPropertyInitialized( field, mappedBy ) ) { Object[] array = field.toArray(); - for ( int i = 0; i < array.length; i++ ) { - if ( argument == null || !argument.contains( array[i] ) ) { - setterNull( array[i], null ); + for ( Object array1 : array ) { + if ( argument == null || !argument.contains( array1 ) ) { + setterNull( array1, null ); } } } @@ -370,9 +370,9 @@ class CodeTemplates { static void exit(@Advice.This Object self, @Advice.Argument(0) Collection argument, @MappedBy String mappedBy) { if ( argument != null && Hibernate.isPropertyInitialized( argument, mappedBy ) ) { Object[] array = argument.toArray(); - for ( int i = 0; i < array.length; i++ ) { - if ( Hibernate.isPropertyInitialized( array[i], mappedBy ) && getter( array[i] ) != self ) { - setterSelf( array[i], self ); + for ( Object array1 : array ) { + if ( Hibernate.isPropertyInitialized( array1, mappedBy ) && getter( array1 ) != self ) { + setterSelf( array1, self ); } } } @@ -399,9 +399,9 @@ class CodeTemplates { static void enter(@FieldValue Map field, @Advice.Argument(0) Map argument, @MappedBy String mappedBy) { if ( field != null && Hibernate.isPropertyInitialized( field, mappedBy ) ) { Object[] array = field.values().toArray(); - for ( int i = 0; i < array.length; i++ ) { - if ( argument == null || !argument.values().contains( array[i] ) ) { - setterNull( array[i], null ); + for ( Object array1 : array ) { + if ( argument == null || !argument.values().contains( array1 ) ) { + setterNull( array1, null ); } } } @@ -411,9 +411,9 @@ class CodeTemplates { static void exit(@Advice.This Object self, @Advice.Argument(0) Map argument, @MappedBy String mappedBy) { if ( argument != null && Hibernate.isPropertyInitialized( argument, mappedBy ) ) { Object[] array = argument.values().toArray(); - for ( int i = 0; i < array.length; i++ ) { - if ( Hibernate.isPropertyInitialized( array[i], mappedBy ) && getter( array[i] ) != self ) { - setterSelf( array[i], self ); + for ( Object array1 : array ) { + if ( Hibernate.isPropertyInitialized( array1, mappedBy ) && getter( array1 ) != self ) { + setterSelf( array1, self ); } } } @@ -467,9 +467,9 @@ class CodeTemplates { static void enter(@Advice.This Object self, @FieldValue Collection field, @Advice.Argument(0) Collection argument, @MappedBy String mappedBy) { if ( field != null && Hibernate.isPropertyInitialized( field, mappedBy ) ) { Object[] array = field.toArray(); - for ( int i = 0; i < array.length; i++ ) { - if ( argument == null || !argument.contains( array[i] ) ) { - getter( array[i] ).remove( self ); + for ( Object array1 : array ) { + if ( argument == null || !argument.contains( array1 ) ) { + getter( array1 ).remove( self ); } } } @@ -479,9 +479,9 @@ class CodeTemplates { static void exit(@Advice.This Object self, @Advice.Argument(0) Collection argument, @MappedBy String mappedBy) { if ( argument != null && Hibernate.isPropertyInitialized( argument, mappedBy ) ) { Object[] array = argument.toArray(); - for ( int i = 0; i < array.length; i++ ) { - if ( Hibernate.isPropertyInitialized( array[i], mappedBy ) ) { - Collection c = getter( array[i] ); + for ( Object array1 : array ) { + if ( Hibernate.isPropertyInitialized( array1, mappedBy ) ) { + Collection c = getter( array1 ); if ( c != self && c != null ) { c.add( self ); } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementHelper.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementHelper.java index 91d865ac49..7c509cfe32 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementHelper.java @@ -8,7 +8,6 @@ package org.hibernate.bytecode.enhance.spi.interceptor; import java.util.Locale; import java.util.function.BiFunction; -import java.util.function.Function; import org.hibernate.FlushMode; import org.hibernate.LazyInitializationException; @@ -31,8 +30,7 @@ public class EnhancementHelper { public static boolean includeInBaseFetchGroup( Property bootMapping, boolean isEnhanced, - boolean allowEnhancementAsProxy, - Function hasSubclassChecker) { + boolean allowEnhancementAsProxy) { final Value value = bootMapping.getValue(); if ( ! isEnhanced ) { @@ -57,7 +55,6 @@ public class EnhancementHelper { } // include it in the base fetch group so long as the config allows // using the FK to create an "enhancement proxy" -// return allowEnhancementAsProxy && hasSubclassChecker.apply( toOne.getReferencedEntityName() ); return allowEnhancementAsProxy; } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributesMetadata.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributesMetadata.java index 380b17711c..8192d3d132 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributesMetadata.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/LazyAttributesMetadata.java @@ -16,7 +16,6 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.function.Function; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; @@ -34,8 +33,7 @@ public class LazyAttributesMetadata implements Serializable { public static LazyAttributesMetadata from( PersistentClass mappedEntity, boolean isEnhanced, - boolean allowEnhancementAsProxy, - Function hasSubclassChecker) { + boolean allowEnhancementAsProxy) { final Map lazyAttributeDescriptorMap = new LinkedHashMap<>(); final Map> fetchGroupToAttributesMap = new HashMap<>(); @@ -48,8 +46,7 @@ public class LazyAttributesMetadata implements Serializable { final boolean lazy = ! EnhancementHelper.includeInBaseFetchGroup( property, isEnhanced, - allowEnhancementAsProxy, - hasSubclassChecker + allowEnhancementAsProxy ); if ( lazy ) { final LazyAttributeDescriptor lazyAttributeDescriptor = LazyAttributeDescriptor.from( property, i, x++ ); 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 e3d35c0b7e..81793fb23d 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 @@ -111,6 +111,7 @@ public class CollectionCacheInvalidator return; } final EntityMetamodel entityMetamodel = persister.getEntityMetamodel(); + final boolean debugEnabled = LOG.isDebugEnabled(); for ( String role : collectionRoles ) { final CollectionPersister collectionPersister = metamodel.collectionPersister( role ); if ( !collectionPersister.hasCache() ) { @@ -142,7 +143,9 @@ public class CollectionCacheInvalidator } } else { - LOG.debug( "Evict CollectionRegion " + role ); + if ( debugEnabled ) { + LOG.debug( "Evict CollectionRegion " + role ); + } final CollectionDataAccess cacheAccessStrategy = collectionPersister.getCacheAccessStrategy(); final SoftLock softLock = cacheAccessStrategy.lockRegion(); session.getActionQueue().registerProcess( (success, session1) -> { diff --git a/hibernate-core/src/main/java/org/hibernate/cache/internal/EnabledCaching.java b/hibernate-core/src/main/java/org/hibernate/cache/internal/EnabledCaching.java index e7257ed0d0..9ce34807a3 100644 --- a/hibernate-core/src/main/java/org/hibernate/cache/internal/EnabledCaching.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/internal/EnabledCaching.java @@ -48,6 +48,7 @@ import org.hibernate.pretty.MessageHelper; /** * @author Steve Ebersole * @author Strong Liu + * @author Gail Badner */ public class EnabledCaching implements CacheImplementor, DomainDataRegionBuildingContext { private static final CoreMessageLogger LOG = CoreLogging.messageLogger( EnabledCaching.class ); @@ -57,6 +58,10 @@ public class EnabledCaching implements CacheImplementor, DomainDataRegionBuildin private final Map regionsByName = new ConcurrentHashMap<>(); + // A map by name for QueryResultsRegion instances that have the same name as a Region + // in #regionsByName. + private final Map queryResultsRegionsByDuplicateName = new ConcurrentHashMap<>(); + private final Map entityAccessMap = new ConcurrentHashMap<>(); private final Map naturalIdAccessMap = new ConcurrentHashMap<>(); private final Map collectionAccessMap = new ConcurrentHashMap<>(); @@ -204,6 +209,8 @@ public class EnabledCaching implements CacheImplementor, DomainDataRegionBuildin @Override public Region getRegion(String regionName) { + // The Region in regionsByName has precedence over the + // QueryResultsRegion in #queryResultsRegionsByDuplicateName return regionsByName.get( regionName ); } @@ -488,12 +495,23 @@ public class EnabledCaching implements CacheImplementor, DomainDataRegionBuildin } protected QueryResultsCache makeQueryResultsRegionAccess(String regionName) { - final QueryResultsRegion region = (QueryResultsRegion) regionsByName.computeIfAbsent( + final Region region = regionsByName.computeIfAbsent( regionName, this::makeQueryResultsRegion ); + final QueryResultsRegion queryResultsRegion; + if ( QueryResultsRegion.class.isInstance( region ) ) { + queryResultsRegion = (QueryResultsRegion) region; + } + else { + // There was already a different type of Region with the same name. + queryResultsRegion = queryResultsRegionsByDuplicateName.computeIfAbsent( + regionName, + this::makeQueryResultsRegion + ); + } final QueryResultsCacheImpl regionAccess = new QueryResultsCacheImpl( - region, + queryResultsRegion, timestampsCache ); namedQueryResultsCacheMap.put( regionName, regionAccess ); @@ -502,20 +520,9 @@ public class EnabledCaching implements CacheImplementor, DomainDataRegionBuildin } protected QueryResultsRegion makeQueryResultsRegion(String regionName) { - // make sure there is not an existing domain-data region with that name.. - final Region existing = regionsByName.get( regionName ); - if ( existing != null ) { - if ( !QueryResultsRegion.class.isInstance( existing ) ) { - throw new IllegalStateException( "Cannot store both domain-data and query-result-data in the same region [" + regionName ); - } - - throw new IllegalStateException( "Illegal call to create QueryResultsRegion - one already existed" ); - } - return regionFactory.buildQueryResultsRegion( regionName, getSessionFactory() ); } - @Override public Set getCacheRegionNames() { return regionsByName.keySet(); @@ -524,6 +531,10 @@ public class EnabledCaching implements CacheImplementor, DomainDataRegionBuildin @Override public void evictRegion(String regionName) { getRegion( regionName ).clear(); + final QueryResultsRegion queryResultsRegionWithDuplicateName = queryResultsRegionsByDuplicateName.get( regionName ); + if ( queryResultsRegionWithDuplicateName != null ) { + queryResultsRegionWithDuplicateName.clear(); + } } @Override @@ -545,6 +556,9 @@ public class EnabledCaching implements CacheImplementor, DomainDataRegionBuildin for ( Region region : regionsByName.values() ) { region.destroy(); } + for ( Region region : queryResultsRegionsByDuplicateName.values() ) { + region.destroy(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/CacheImplementor.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/CacheImplementor.java index 00a28c7b99..c77b13db7b 100644 --- a/hibernate-core/src/main/java/org/hibernate/cache/spi/CacheImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/CacheImplementor.java @@ -52,7 +52,9 @@ public interface CacheImplementor extends Service, Cache, org.hibernate.engine.s void prime(Set cacheRegionConfigs); /** - * Get a cache Region by name + * Get a cache Region by name. If there is both a {@link DomainDataRegion} + * and a {@link QueryResultsRegion} with the specified name, then the + * {@link DomainDataRegion} will be returned. * * @apiNote It is only valid to call this method after {@link #prime} has * been performed diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/support/AbstractDomainDataRegion.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/AbstractDomainDataRegion.java index a76eacfc1c..568757c580 100644 --- a/hibernate-core/src/main/java/org/hibernate/cache/spi/support/AbstractDomainDataRegion.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/support/AbstractDomainDataRegion.java @@ -7,8 +7,9 @@ package org.hibernate.cache.spi.support; import java.util.Collections; +import java.util.HashMap; +import java.util.List; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; import org.hibernate.cache.CacheException; import org.hibernate.cache.cfg.spi.CollectionDataCachingConfig; @@ -121,15 +122,16 @@ public abstract class AbstractDomainDataRegion extends AbstractRegion implements private Map generateEntityDataAccessMap( DomainDataRegionConfig regionConfig) { - if ( regionConfig.getEntityCaching().isEmpty() ) { + final List entityCaching = regionConfig.getEntityCaching(); + if ( entityCaching.isEmpty() ) { return Collections.emptyMap(); } - final Map accessMap = new ConcurrentHashMap<>(); - for ( EntityDataCachingConfig entityAccessConfig : regionConfig.getEntityCaching() ) { - accessMap.computeIfAbsent( + final Map accessMap = new HashMap<>( entityCaching.size() ); + for ( EntityDataCachingConfig entityAccessConfig : entityCaching ) { + accessMap.put( entityAccessConfig.getNavigableRole(), - hierarchy -> generateEntityAccess( entityAccessConfig ) + generateEntityAccess( entityAccessConfig ) ); } @@ -137,15 +139,16 @@ public abstract class AbstractDomainDataRegion extends AbstractRegion implements } private Map generateNaturalIdDataAccessMap(DomainDataRegionConfig regionConfig) { - if ( regionConfig.getNaturalIdCaching().isEmpty() ) { + final List naturalIdCaching = regionConfig.getNaturalIdCaching(); + if ( naturalIdCaching.isEmpty() ) { return Collections.emptyMap(); } - final Map accessMap = new ConcurrentHashMap<>(); - for ( NaturalIdDataCachingConfig naturalIdAccessConfig : regionConfig.getNaturalIdCaching() ) { - accessMap.computeIfAbsent( + final Map accessMap = new HashMap<>( naturalIdCaching.size() ); + for ( NaturalIdDataCachingConfig naturalIdAccessConfig : naturalIdCaching ) { + accessMap.put( naturalIdAccessConfig.getNavigableRole(), - hierarchy -> generateNaturalIdAccess( naturalIdAccessConfig ) + generateNaturalIdAccess( naturalIdAccessConfig ) ); } @@ -154,15 +157,16 @@ public abstract class AbstractDomainDataRegion extends AbstractRegion implements private Map generateCollectionDataAccessMap( DomainDataRegionConfig regionConfig) { - if ( regionConfig.getCollectionCaching().isEmpty() ) { + final List collectionCaching = regionConfig.getCollectionCaching(); + if ( collectionCaching.isEmpty() ) { return Collections.emptyMap(); } - final Map accessMap = new ConcurrentHashMap<>(); - for ( CollectionDataCachingConfig cachingConfig : regionConfig.getCollectionCaching() ) { - accessMap.computeIfAbsent( + final Map accessMap = new HashMap<>( collectionCaching.size() ); + for ( CollectionDataCachingConfig cachingConfig : collectionCaching ) { + accessMap.put( cachingConfig.getNavigableRole(), - hierarchy -> generateCollectionAccess( cachingConfig ) + generateCollectionAccess( cachingConfig ) ); } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java b/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java index 21852897e8..140ee68ed0 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java @@ -2060,4 +2060,19 @@ public interface AvailableSettings extends org.hibernate.jpa.AvailableSettings { * @since 5.4 */ String SEQUENCE_INCREMENT_SIZE_MISMATCH_STRATEGY = "hibernate.id.sequence.increment_size_mismatch_strategy"; + + /** + *

+ * When you use {@link javax.persistence.InheritanceType#JOINED} strategy for inheritance mapping and query + * a value from an entity, all superclass tables are joined in the query regardless you need them. With + * this setting set to true only superclass tables which are really needed are joined. + *

+ *

+ * The default value is true. + *

+ * + * @since 5.4 + */ + String OMIT_JOIN_OF_SUPERCLASS_TABLES = "hibernate.query.omit_join_of_superclass_tables"; + } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/BaselineSessionEventsListenerBuilder.java b/hibernate-core/src/main/java/org/hibernate/cfg/BaselineSessionEventsListenerBuilder.java index 5a2b8a4190..487b201c25 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/BaselineSessionEventsListenerBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/BaselineSessionEventsListenerBuilder.java @@ -7,6 +7,7 @@ package org.hibernate.cfg; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import org.hibernate.HibernateException; @@ -17,6 +18,9 @@ import org.hibernate.engine.internal.StatisticalLoggingSessionEventListener; * @author Steve Ebersole */ public class BaselineSessionEventsListenerBuilder { + + private static final SessionEventListener[] EMPTY = new SessionEventListener[0]; + private boolean logSessionMetrics; private Class autoListener; @@ -33,6 +37,10 @@ public class BaselineSessionEventsListenerBuilder { } @SuppressWarnings("UnusedDeclaration") + /** + * @deprecated this method will be removed as this builder should become immutable + */ + @Deprecated public void setLogSessionMetrics(boolean logSessionMetrics) { this.logSessionMetrics = logSessionMetrics; } @@ -43,26 +51,59 @@ public class BaselineSessionEventsListenerBuilder { } @SuppressWarnings("UnusedDeclaration") + /** + * @deprecated this method will be removed as this builder should become immutable + */ + @Deprecated public void setAutoListener(Class autoListener) { this.autoListener = autoListener; } public List buildBaselineList() { - List list = new ArrayList(); - if ( logSessionMetrics && StatisticalLoggingSessionEventListener.isLoggingEnabled() ) { - list.add( new StatisticalLoggingSessionEventListener() ); - } - if ( autoListener != null ) { - try { - list.add( autoListener.newInstance() ); - } - catch (Exception e) { - throw new HibernateException( - "Unable to instantiate specified auto SessionEventListener : " + autoListener.getName(), - e - ); - } - } + final SessionEventListener[] sessionEventListeners = buildBaseline(); + //Capacity: needs to hold at least all elements from the baseline, but also expect to add a little more later. + ArrayList list = new ArrayList<>( sessionEventListeners.length + 3 ); + Collections.addAll( list, sessionEventListeners ); return list; } + + public SessionEventListener[] buildBaseline() { + final boolean addStats = logSessionMetrics && StatisticalLoggingSessionEventListener.isLoggingEnabled(); + final boolean addAutoListener = autoListener != null; + final SessionEventListener[] arr; + if ( addStats && addAutoListener ) { + arr = new SessionEventListener[2]; + arr[0] = buildStatsListener(); + arr[1] = buildAutoListener( autoListener ); + } + else if ( !addStats && !addAutoListener ) { + arr = EMPTY; + } + else if ( !addStats && addAutoListener ) { + arr = new SessionEventListener[1]; + arr[0] = buildAutoListener( autoListener ); + } + else { //Last case: if ( addStats && !addAutoListener ) + arr = new SessionEventListener[1]; + arr[0] = buildStatsListener(); + } + return arr; + } + + private static SessionEventListener buildAutoListener(final Class autoListener) { + try { + return autoListener.newInstance(); + } + catch (Exception e) { + throw new HibernateException( + "Unable to instantiate specified auto SessionEventListener : " + autoListener.getName(), + e + ); + } + } + + private static SessionEventListener buildStatsListener() { + return new StatisticalLoggingSessionEventListener(); + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/BinderHelper.java b/hibernate-core/src/main/java/org/hibernate/cfg/BinderHelper.java index 8147bd8ed5..3e7ea54c68 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/BinderHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/BinderHelper.java @@ -1124,19 +1124,19 @@ public class BinderHelper { public static Map toAliasTableMap(SqlFragmentAlias[] aliases){ Map ret = new HashMap<>(); - for ( int i = 0; i < aliases.length; i++ ) { - if ( StringHelper.isNotEmpty( aliases[i].table() ) ) { - ret.put( aliases[i].alias(), aliases[i].table() ); -} + for ( SqlFragmentAlias aliase : aliases ) { + if ( StringHelper.isNotEmpty( aliase.table() ) ) { + ret.put( aliase.alias(), aliase.table() ); + } } return ret; } public static Map toAliasEntityMap(SqlFragmentAlias[] aliases){ Map ret = new HashMap<>(); - for ( int i = 0; i < aliases.length; i++ ) { - if ( aliases[i].entity() != void.class ) { - ret.put( aliases[i].alias(), aliases[i].entity().getName() ); + for ( SqlFragmentAlias aliase : aliases ) { + if ( aliase.entity() != void.class ) { + ret.put( aliase.alias(), aliase.entity().getName() ); } } return ret; diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/ExternalSessionFactoryConfig.java b/hibernate-core/src/main/java/org/hibernate/cfg/ExternalSessionFactoryConfig.java index 6cb5815061..4121cb2bfe 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/ExternalSessionFactoryConfig.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/ExternalSessionFactoryConfig.java @@ -247,8 +247,8 @@ public abstract class ExternalSessionFactoryConfig { String[] mappingFiles = ConfigurationHelper.toStringArray( mapResources, " ,\n\t\r\f" ); - for ( int i = 0; i < mappingFiles.length; i++ ) { - cfg.addResource( mappingFiles[i] ); + for ( String mappingFile : mappingFiles ) { + cfg.addResource( mappingFile ); } return cfg; diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/IndexOrUniqueKeySecondPass.java b/hibernate-core/src/main/java/org/hibernate/cfg/IndexOrUniqueKeySecondPass.java index 6bdc9edef3..d5ae2c38b6 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/IndexOrUniqueKeySecondPass.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/IndexOrUniqueKeySecondPass.java @@ -66,8 +66,8 @@ public class IndexOrUniqueKeySecondPass implements SecondPass { @Override public void doSecondPass(Map persistentClasses) throws MappingException { if ( columns != null ) { - for ( int i = 0; i < columns.length; i++ ) { - addConstraintToColumn( columns[i] ); + for ( String column1 : columns ) { + addConstraintToColumn( column1 ); } } if ( column != null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/Settings.java b/hibernate-core/src/main/java/org/hibernate/cfg/Settings.java index 046fa59c46..c2d590d984 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/Settings.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/Settings.java @@ -76,7 +76,7 @@ public final class Settings { LOG.debugf( "Check Nullability in Core (should be disabled when Bean Validation is on): %s", enabledDisabled( sessionFactoryOptions.isCheckNullability() ) ); LOG.debugf( "Allow initialization of lazy state outside session : %s", enabledDisabled( sessionFactoryOptions.isInitializeLazyStateOutsideTransactionsEnabled() ) ); - LOG.debugf( "Using BatchFetchStyle : " + sessionFactoryOptions.getBatchFetchStyle().name() ); + LOG.debugf( "Using BatchFetchStyle : %", sessionFactoryOptions.getBatchFetchStyle().name() ); LOG.debugf( "Default batch fetch size: %s", sessionFactoryOptions.getDefaultBatchFetchSize() ); LOG.debugf( "Maximum outer join fetch depth: %s", sessionFactoryOptions.getMaximumFetchDepth() ); LOG.debugf( "Default null ordering: %s", sessionFactoryOptions.getDefaultNullPrecedence() ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/MySQL57Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/MySQL57Dialect.java index 2adf0514bc..8f9ac9742a 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/MySQL57Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/MySQL57Dialect.java @@ -9,7 +9,9 @@ package org.hibernate.dialect; import java.sql.Types; import org.hibernate.dialect.function.SQLFunction; +import org.hibernate.dialect.function.StandardSQLFunction; import org.hibernate.dialect.function.StaticPrecisionFspTimestampFunction; +import org.hibernate.type.StandardBasicTypes; /** * @author Gail Badner @@ -64,6 +66,11 @@ public class MySQL57Dialect extends MySQL55Dialect { // from_unixtime(), timestamp() are functions that return TIMESTAMP that do not support a // fractional seconds precision argument (so there's no need to override them here): + + registerFunction( "weight_string", new StandardSQLFunction( "weight_string", StandardBasicTypes.STRING ) ); + + registerFunction( "to_base64", new StandardSQLFunction( "to_base64", StandardBasicTypes.STRING ) ); + registerFunction( "from_base64", new StandardSQLFunction( "from_base64", StandardBasicTypes.STRING ) ); } /** diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/MySQL8Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/MySQL8Dialect.java index 4d145c11fc..dcda2359b6 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/MySQL8Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/MySQL8Dialect.java @@ -7,6 +7,8 @@ package org.hibernate.dialect; import org.hibernate.LockOptions; +import org.hibernate.dialect.function.StandardSQLFunction; +import org.hibernate.type.StandardBasicTypes; /** * @author Vlad Mihalcea @@ -33,6 +35,10 @@ public class MySQL8Dialect extends MySQL57Dialect { registerKeyword("PERSIST_ONLY"); registerKeyword("RANK"); registerKeyword("ROW_NUMBER"); + + registerFunction( "regexp_replace", new StandardSQLFunction( "regexp_replace", StandardBasicTypes.STRING ) ); + registerFunction( "regexp_instr", new StandardSQLFunction( "regexp_instr", StandardBasicTypes.INTEGER ) ); + registerFunction( "regexp_substr", new StandardSQLFunction( "regexp_substr", StandardBasicTypes.STRING ) ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASE15Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASE15Dialect.java index 4d75e1bdc3..0027263e19 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASE15Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASE15Dialect.java @@ -431,4 +431,9 @@ public class SybaseASE15Dialect extends SybaseDialect { public boolean supportsLockTimeouts() { return false; } + + @Override + public boolean supportsPartitionBy() { + return false; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticReadSelectLockingStrategy.java b/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticReadSelectLockingStrategy.java index 0b1b6d3d2f..319967ba82 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticReadSelectLockingStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticReadSelectLockingStrategy.java @@ -73,17 +73,12 @@ public class PessimisticReadSelectLockingStrategy extends AbstractSelectLockingS } final ResultSet rs = jdbcCoordinator.getResultSetReturn().extract( st ); - try { - if ( !rs.next() ) { - final StatisticsImplementor statistics = factory.getStatistics(); - if ( statistics.isStatisticsEnabled() ) { - statistics.optimisticFailure( lockable.getEntityName() ); - } - throw new StaleObjectStateException( lockable.getEntityName(), id ); + if ( !rs.next() ) { + final StatisticsImplementor statistics = factory.getStatistics(); + if ( statistics.isStatisticsEnabled() ) { + statistics.optimisticFailure( lockable.getEntityName() ); } - } - finally { - jdbcCoordinator.getLogicalConnection().getResourceRegistry().release( rs, st ); + throw new StaleObjectStateException( lockable.getEntityName(), id ); } } finally { diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/AbstractEntityEntry.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/AbstractEntityEntry.java index c5045b4f46..f3c8e4f240 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/AbstractEntityEntry.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/AbstractEntityEntry.java @@ -9,6 +9,7 @@ package org.hibernate.engine.internal; import java.io.IOException; import java.io.ObjectOutputStream; import java.io.Serializable; +import java.util.function.Supplier; import org.hibernate.AssertionFailure; import org.hibernate.CustomEntityDirtinessStrategy; @@ -309,7 +310,7 @@ public abstract class AbstractEntityEntry implements Serializable, EntityEntry { return !isExistsInDatabase(); } else { - return session.getPersistenceContextInternal().getNullifiableEntityKeys().contains( getEntityKey() ); + return session.getPersistenceContextInternal().containsNullifiableEntityKey( this::getEntityKey ); } } 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 860350e038..814cccf238 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 @@ -160,7 +160,7 @@ public final class ForeignKeys { if ( isDelete && value == LazyPropertyInitializer.UNFETCHED_PROPERTY && type.isEntityType() && - !session.getPersistenceContextInternal().getNullifiableEntityKeys().isEmpty() ) { + !session.getPersistenceContextInternal().isNullifiableEntityKeysEmpty() ) { // IMPLEMENTATION NOTE: If cascade-remove was mapped for the attribute, // then value should have been initialized previously, when the remove operation was // cascaded to the property (because CascadingAction.DELETE.performOnLazyProperty() diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/JoinSequence.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/JoinSequence.java index bf2d699ea8..83c6d1cbdd 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/JoinSequence.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/JoinSequence.java @@ -47,6 +47,7 @@ public class JoinSequence { private Selector selector; private JoinSequence next; private boolean isFromPart; + private Set queryReferencedTables; /** * Constructs a JoinSequence @@ -466,7 +467,7 @@ public class JoinSequence { Set treatAsDeclarations) { final boolean include = includeSubclassJoins && isIncluded( alias ); joinFragment.addJoins( - joinable.fromJoinFragment( alias, innerJoin, include, treatAsDeclarations ), + joinable.fromJoinFragment( alias, innerJoin, include, treatAsDeclarations, queryReferencedTables ), joinable.whereJoinFragment( alias, innerJoin, include, treatAsDeclarations ) ); } @@ -573,6 +574,15 @@ public class JoinSequence { return useThetaStyle; } + /** + * Set all tables the query refers to. It allows to optimize the query. + * + * @param queryReferencedTables + */ + public void setQueryReferencedTables(Set queryReferencedTables) { + this.queryReferencedTables = queryReferencedTables; + } + public Join getFirstJoin() { return joins.get( 0 ); } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/SessionEventListenerManagerImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/SessionEventListenerManagerImpl.java index a79dac9657..52261bfeab 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/SessionEventListenerManagerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/SessionEventListenerManagerImpl.java @@ -7,8 +7,8 @@ package org.hibernate.engine.internal; import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; +import java.util.Arrays; +import java.util.Objects; import org.hibernate.SessionEventListener; import org.hibernate.engine.spi.SessionEventListenerManager; @@ -17,255 +17,269 @@ import org.hibernate.engine.spi.SessionEventListenerManager; * @author Steve Ebersole */ public class SessionEventListenerManagerImpl implements SessionEventListenerManager, Serializable { - private List listenerList; + + private SessionEventListener[] listeners; + + public SessionEventListenerManagerImpl(SessionEventListener... initialListener) { + //no need for defensive copies until the array is mutated: + this.listeners = initialListener; + } @Override - public void addListener(SessionEventListener... listeners) { - if ( listenerList == null ) { - listenerList = new ArrayList<>(); + public void addListener(final SessionEventListener... additionalListeners) { + Objects.requireNonNull( additionalListeners ); + final SessionEventListener[] existing = this.listeners; + if ( existing == null ) { + //Make a defensive copy as this array can be tracked back to API (user code) + this.listeners = Arrays.copyOf( additionalListeners, additionalListeners.length ); + } + else { + // Resize our existing array and add the new listeners + final SessionEventListener[] newlist = new SessionEventListener[ existing.length + additionalListeners.length ]; + System.arraycopy( existing, 0, newlist, 0, existing.length ); + System.arraycopy( additionalListeners, 0, newlist, existing.length, additionalListeners.length ); + this.listeners = newlist; } - - java.util.Collections.addAll( listenerList, listeners ); } @Override public void transactionCompletion(boolean successful) { - if ( listenerList == null ) { + if ( listeners == null ) { return; } - for ( SessionEventListener listener : listenerList ) { + for ( SessionEventListener listener : listeners ) { listener.transactionCompletion( successful ); } } @Override public void jdbcConnectionAcquisitionStart() { - if ( listenerList == null ) { + if ( listeners == null ) { return; } - for ( SessionEventListener listener : listenerList ) { + for ( SessionEventListener listener : listeners ) { listener.jdbcConnectionAcquisitionStart(); } } @Override public void jdbcConnectionAcquisitionEnd() { - if ( listenerList == null ) { + if ( listeners == null ) { return; } - for ( SessionEventListener listener : listenerList ) { + for ( SessionEventListener listener : listeners ) { listener.jdbcConnectionAcquisitionEnd(); } } @Override public void jdbcConnectionReleaseStart() { - if ( listenerList == null ) { + if ( listeners == null ) { return; } - for ( SessionEventListener listener : listenerList ) { + for ( SessionEventListener listener : listeners ) { listener.jdbcConnectionReleaseStart(); } } @Override public void jdbcConnectionReleaseEnd() { - if ( listenerList == null ) { + if ( listeners == null ) { return; } - for ( SessionEventListener listener : listenerList ) { + for ( SessionEventListener listener : listeners ) { listener.jdbcConnectionReleaseEnd(); } } @Override public void jdbcPrepareStatementStart() { - if ( listenerList == null ) { + if ( listeners == null ) { return; } - for ( SessionEventListener listener : listenerList ) { + for ( SessionEventListener listener : listeners ) { listener.jdbcPrepareStatementStart(); } } @Override public void jdbcPrepareStatementEnd() { - if ( listenerList == null ) { + if ( listeners == null ) { return; } - for ( SessionEventListener listener : listenerList ) { + for ( SessionEventListener listener : listeners ) { listener.jdbcPrepareStatementEnd(); } } @Override public void jdbcExecuteStatementStart() { - if ( listenerList == null ) { + if ( listeners == null ) { return; } - for ( SessionEventListener listener : listenerList ) { + for ( SessionEventListener listener : listeners ) { listener.jdbcExecuteStatementStart(); } } @Override public void jdbcExecuteStatementEnd() { - if ( listenerList == null ) { + if ( listeners == null ) { return; } - for ( SessionEventListener listener : listenerList ) { + for ( SessionEventListener listener : listeners ) { listener.jdbcExecuteStatementEnd(); } } @Override public void jdbcExecuteBatchStart() { - if ( listenerList == null ) { + if ( listeners == null ) { return; } - for ( SessionEventListener listener : listenerList ) { + for ( SessionEventListener listener : listeners ) { listener.jdbcExecuteBatchStart(); } } @Override public void jdbcExecuteBatchEnd() { - if ( listenerList == null ) { + if ( listeners == null ) { return; } - for ( SessionEventListener listener : listenerList ) { + for ( SessionEventListener listener : listeners ) { listener.jdbcExecuteBatchEnd(); } } @Override public void cachePutStart() { - if ( listenerList == null ) { + if ( listeners == null ) { return; } - for ( SessionEventListener listener : listenerList ) { + for ( SessionEventListener listener : listeners ) { listener.cachePutStart(); } } @Override public void cachePutEnd() { - if ( listenerList == null ) { + if ( listeners == null ) { return; } - for ( SessionEventListener listener : listenerList ) { + for ( SessionEventListener listener : listeners ) { listener.cachePutEnd(); } } @Override public void cacheGetStart() { - if ( listenerList == null ) { + if ( listeners == null ) { return; } - for ( SessionEventListener listener : listenerList ) { + for ( SessionEventListener listener : listeners ) { listener.cacheGetStart(); } } @Override public void cacheGetEnd(boolean hit) { - if ( listenerList == null ) { + if ( listeners == null ) { return; } - for ( SessionEventListener listener : listenerList ) { + for ( SessionEventListener listener : listeners ) { listener.cacheGetEnd( hit ); } } @Override public void flushStart() { - if ( listenerList == null ) { + if ( listeners == null ) { return; } - for ( SessionEventListener listener : listenerList ) { + for ( SessionEventListener listener : listeners ) { listener.flushStart(); } } @Override public void flushEnd(int numberOfEntities, int numberOfCollections) { - if ( listenerList == null ) { + if ( listeners == null ) { return; } - for ( SessionEventListener listener : listenerList ) { + for ( SessionEventListener listener : listeners ) { listener.flushEnd( numberOfEntities, numberOfCollections ); } } @Override public void partialFlushStart() { - if ( listenerList == null ) { + if ( listeners == null ) { return; } - for ( SessionEventListener listener : listenerList ) { + for ( SessionEventListener listener : listeners ) { listener.partialFlushStart(); } } @Override public void partialFlushEnd(int numberOfEntities, int numberOfCollections) { - if ( listenerList == null ) { + if ( listeners == null ) { return; } - for ( SessionEventListener listener : listenerList ) { + for ( SessionEventListener listener : listeners ) { listener.partialFlushEnd( numberOfEntities, numberOfCollections ); } } @Override public void dirtyCalculationStart() { - if ( listenerList == null ) { + if ( listeners == null ) { return; } - for ( SessionEventListener listener : listenerList ) { + for ( SessionEventListener listener : listeners ) { listener.dirtyCalculationStart(); } } @Override public void dirtyCalculationEnd(boolean dirty) { - if ( listenerList == null ) { + if ( listeners == null ) { return; } - for ( SessionEventListener listener : listenerList ) { + for ( SessionEventListener listener : listeners ) { listener.dirtyCalculationEnd( dirty ); } } @Override public void end() { - if ( listenerList == null ) { + if ( listeners == null ) { return; } - for ( SessionEventListener listener : listenerList ) { + for ( SessionEventListener listener : listeners ) { listener.end(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java index b071c9a9f7..93f7d9cc4b 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java @@ -13,6 +13,7 @@ import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.IdentityHashMap; @@ -21,6 +22,8 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentMap; +import java.util.function.BiConsumer; +import java.util.function.Supplier; import org.hibernate.AssertionFailure; import org.hibernate.Hibernate; @@ -58,6 +61,7 @@ import org.hibernate.event.spi.EventSource; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.collections.ConcurrentReferenceHashMap; import org.hibernate.internal.util.collections.IdentityMap; +import org.hibernate.metamodel.spi.MetamodelImplementor; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.pretty.MessageHelper; @@ -78,6 +82,7 @@ import org.jboss.logging.Logger; * their processing. * * @author Steve Ebersole + * @author Sanne Grinovero */ public class StatefulPersistenceContext implements PersistenceContext { private static final CoreMessageLogger LOG = Logger.getMessageLogger( @@ -96,7 +101,6 @@ public class StatefulPersistenceContext implements PersistenceContext { private Map entitiesByUniqueKey; private EntityEntryContext entityEntryContext; -// private Map entityEntries; // Entity proxies, by EntityKey private ConcurrentMap proxiesByKey; @@ -153,30 +157,25 @@ public class StatefulPersistenceContext implements PersistenceContext { this.session = session; entitiesByKey = new HashMap<>( INIT_COLL_SIZE ); - entitiesByUniqueKey = new HashMap<>( INIT_COLL_SIZE ); - //noinspection unchecked - proxiesByKey = new ConcurrentReferenceHashMap<>( - INIT_COLL_SIZE, - .75f, - 1, - ConcurrentReferenceHashMap.ReferenceType.STRONG, - ConcurrentReferenceHashMap.ReferenceType.WEAK, - null - ); entitySnapshotsByKey = new HashMap<>( INIT_COLL_SIZE ); entityEntryContext = new EntityEntryContext( this ); -// entityEntries = IdentityMap.instantiateSequenced( INIT_COLL_SIZE ); - collectionEntries = IdentityMap.instantiateSequenced( INIT_COLL_SIZE ); - parentsByChild = new IdentityHashMap<>( INIT_COLL_SIZE ); - collectionsByKey = new HashMap<>( INIT_COLL_SIZE ); - arrayHolders = new IdentityHashMap<>( INIT_COLL_SIZE ); + } - nullifiableEntityKeys = new HashSet<>(); - - nullAssociations = new HashSet<>( INIT_COLL_SIZE ); - nonlazyCollections = new ArrayList<>( INIT_COLL_SIZE ); + private ConcurrentMap getOrInitializeProxiesByKey() { + if ( proxiesByKey == null ) { + //noinspection unchecked + proxiesByKey = new ConcurrentReferenceHashMap<>( + INIT_COLL_SIZE, + .75f, + 1, + ConcurrentReferenceHashMap.ReferenceType.STRONG, + ConcurrentReferenceHashMap.ReferenceType.WEAK, + null + ); + } + return proxiesByKey; } @Override @@ -199,7 +198,7 @@ public class StatefulPersistenceContext implements PersistenceContext { @Override public void addUnownedCollection(CollectionKey key, PersistentCollection collection) { - if (unownedCollections==null) { + if ( unownedCollections == null ) { unownedCollections = new HashMap<>( INIT_COLL_SIZE ); } unownedCollections.put( key, collection ); @@ -212,20 +211,20 @@ public class StatefulPersistenceContext implements PersistenceContext { @Override public BatchFetchQueue getBatchFetchQueue() { - if (batchFetchQueue==null) { - batchFetchQueue = new BatchFetchQueue(this); + if ( batchFetchQueue == null ) { + batchFetchQueue = new BatchFetchQueue( this ); } return batchFetchQueue; } @Override public void clear() { - for ( Object o : proxiesByKey.values() ) { - if ( o == null ) { - //entry may be GCd - continue; - } - ((HibernateProxy) o).getHibernateLazyInitializer().unsetSession(); + if ( proxiesByKey != null ) { + proxiesByKey.forEach( (k,o) -> { + if ( o != null) { + ((HibernateProxy) o).getHibernateLazyInitializer().unsetSession(); + } + } ); } for ( Entry objectEntityEntryEntry : entityEntryContext.reentrantSafeEntityEntries() ) { @@ -238,22 +237,22 @@ public class StatefulPersistenceContext implements PersistenceContext { } final SharedSessionContractImplementor session = getSession(); - IdentityMap.onEachKey( collectionEntries, k -> k.unsetSession( session ) ); + if ( collectionEntries != null ) { + IdentityMap.onEachKey( collectionEntries, k -> k.unsetSession( session ) ); + } - arrayHolders.clear(); + arrayHolders = null; entitiesByKey.clear(); - entitiesByUniqueKey.clear(); + entitiesByUniqueKey = null; entityEntryContext.clear(); -// entityEntries.clear(); - parentsByChild.clear(); + parentsByChild = null; entitySnapshotsByKey.clear(); collectionsByKey.clear(); - collectionEntries.clear(); - if ( unownedCollections != null ) { - unownedCollections.clear(); - } - proxiesByKey.clear(); - nullifiableEntityKeys.clear(); + nonlazyCollections = null; + collectionEntries = null; + unownedCollections = null; + proxiesByKey = null; + nullifiableEntityKeys = null; if ( batchFetchQueue != null ) { batchFetchQueue.clear(); } @@ -262,7 +261,7 @@ public class StatefulPersistenceContext implements PersistenceContext { if ( loadContexts != null ) { loadContexts.cleanup(); } - naturalIdXrefDelegate.clear(); + naturalIdXrefDelegate = null; } @Override @@ -295,11 +294,8 @@ public class StatefulPersistenceContext implements PersistenceContext { @Override public void afterTransactionCompletion() { cleanUpInsertedKeysAfterTransaction(); + // Downgrade locks entityEntryContext.downgradeLocks(); -// // Downgrade locks -// for ( EntityEntry o : entityEntries.values() ) { -// o.setLockMode( LockMode.NONE ); -// } } /** @@ -406,30 +402,38 @@ public class StatefulPersistenceContext implements PersistenceContext { @Override public Object removeEntity(EntityKey key) { final Object entity = entitiesByKey.remove( key ); - final Iterator itr = entitiesByUniqueKey.values().iterator(); - while ( itr.hasNext() ) { - if ( itr.next() == entity ) { - itr.remove(); + if ( entitiesByUniqueKey != null ) { + final Iterator itr = entitiesByUniqueKey.values().iterator(); + while ( itr.hasNext() ) { + if ( itr.next() == entity ) { + itr.remove(); + } } } + // Clear all parent cache - parentsByChild.clear(); + parentsByChild = null; entitySnapshotsByKey.remove( key ); - nullifiableEntityKeys.remove( key ); + if ( nullifiableEntityKeys != null ) { + nullifiableEntityKeys.remove( key ); + } if( batchFetchQueue != null ) { - getBatchFetchQueue().removeBatchLoadableEntityKey(key); - getBatchFetchQueue().removeSubselect(key); + getBatchFetchQueue().removeBatchLoadableEntityKey( key ); + getBatchFetchQueue().removeSubselect( key ); } return entity; } @Override public Object getEntity(EntityUniqueKey euk) { - return entitiesByUniqueKey.get( euk ); + return entitiesByUniqueKey == null ? null : entitiesByUniqueKey.get( euk ); } @Override public void addEntity(EntityUniqueKey euk, Object entity) { + if ( entitiesByUniqueKey == null ) { + entitiesByUniqueKey = new HashMap<>( INIT_COLL_SIZE ); + } entitiesByUniqueKey.put( euk, entity ); } @@ -450,7 +454,7 @@ public class StatefulPersistenceContext implements PersistenceContext { @Override public CollectionEntry getCollectionEntry(PersistentCollection coll) { - return collectionEntries.get( coll ); + return collectionEntries == null ? null : collectionEntries.get( coll ); } @Override @@ -559,12 +563,12 @@ public class StatefulPersistenceContext implements PersistenceContext { @Override public boolean containsCollection(PersistentCollection collection) { - return collectionEntries.containsKey( collection ); + return collectionEntries != null && collectionEntries.containsKey( collection ); } @Override public boolean containsProxy(Object entity) { - return proxiesByKey.containsValue( entity ); + return proxiesByKey != null && proxiesByKey.containsValue( entity ); } @Override @@ -616,7 +620,7 @@ public class StatefulPersistenceContext implements PersistenceContext { final EntityPersister persister = session.getFactory().getMetamodel().entityPersister( li.getEntityName() ); final EntityKey key = session.generateEntityKey( li.getIdentifier(), persister ); // any earlier proxy takes precedence - proxiesByKey.putIfAbsent( key, proxy ); + getOrInitializeProxiesByKey().putIfAbsent( key, proxy ); proxy.getHibernateLazyInitializer().setSession( session ); } } @@ -686,7 +690,7 @@ public class StatefulPersistenceContext implements PersistenceContext { // If an impl is passed, there is really no point in creating a proxy. // It would just be extra processing. Just return the impl if ( object != null ) { - proxiesByKey.remove( key ); + removeProxyByKey( key ); return object; } @@ -697,7 +701,7 @@ public class StatefulPersistenceContext implements PersistenceContext { final Object impl = originalHibernateProxy.getHibernateLazyInitializer().getImplementation(); // can we return it? if ( concreteProxyClass.isInstance( impl ) ) { - proxiesByKey.remove( key ); + removeProxyByKey( key ); return impl; } } @@ -722,12 +726,19 @@ public class StatefulPersistenceContext implements PersistenceContext { } } + private Object removeProxyByKey(final EntityKey key) { + if ( proxiesByKey != null ) { + return proxiesByKey.remove( key ); + } + return null; + } + @Override public Object proxyFor(EntityPersister persister, EntityKey key, Object impl) throws HibernateException { if ( !persister.hasProxy() ) { return impl; } - final Object proxy = proxiesByKey.get( key ); + final Object proxy = getProxy( key ); return ( proxy != null ) ? narrowProxy( proxy, persister, key, impl ) : impl; } @@ -877,7 +888,7 @@ public class StatefulPersistenceContext implements PersistenceContext { * @param key The key of the collection's entry. */ private void addCollection(PersistentCollection coll, CollectionEntry entry, Serializable key) { - collectionEntries.put( coll, entry ); + getOrInitializeCollectionEntries().put( coll, entry ); final CollectionKey collectionKey = new CollectionKey( entry.getLoadedPersister(), key ); final PersistentCollection old = collectionsByKey.put( collectionKey, coll ); if ( old != null ) { @@ -886,12 +897,21 @@ public class StatefulPersistenceContext implements PersistenceContext { } // or should it actually throw an exception? old.unsetSession( session ); - collectionEntries.remove( old ); + if ( collectionEntries != null ) { + collectionEntries.remove( old ); + } // watch out for a case where old is still referenced // somewhere in the object graph! (which is a user error) } } + private IdentityMap getOrInitializeCollectionEntries() { + if ( this.collectionEntries == null ) { + this.collectionEntries = IdentityMap.instantiateSequenced( INIT_COLL_SIZE ); + } + return this.collectionEntries; + } + /** * Add a collection to the cache, creating a new collection entry for it * @@ -900,7 +920,7 @@ public class StatefulPersistenceContext implements PersistenceContext { */ private void addCollection(PersistentCollection collection, CollectionPersister persister) { final CollectionEntry ce = new CollectionEntry( persister, collection ); - collectionEntries.put( collection, ce ); + getOrInitializeCollectionEntries().put( collection, ce ); } @Override @@ -932,22 +952,23 @@ public class StatefulPersistenceContext implements PersistenceContext { @Override public void addNonLazyCollection(PersistentCollection collection) { + if ( nonlazyCollections == null ) { + nonlazyCollections = new ArrayList<>( INIT_COLL_SIZE ); + } nonlazyCollections.add( collection ); } @Override public void initializeNonLazyCollections() throws HibernateException { if ( loadCounter == 0 ) { - if ( LOG.isTraceEnabled() ) { - LOG.trace( "Initializing non-lazy collections" ); - } + LOG.trace( "Initializing non-lazy collections" ); //do this work only at the very highest level of the load //don't let this method be called recursively loadCounter++; try { int size; - while ( ( size = nonlazyCollections.size() ) > 0 ) { + while ( nonlazyCollections != null && ( size = nonlazyCollections.size() ) > 0 ) { //note that each iteration of the loop may add new elements nonlazyCollections.remove( size - 1 ).forceInitialization(); } @@ -961,18 +982,21 @@ public class StatefulPersistenceContext implements PersistenceContext { @Override public PersistentCollection getCollectionHolder(Object array) { - return arrayHolders.get( array ); + return arrayHolders == null ? null : arrayHolders.get( array ); } @Override public void addCollectionHolder(PersistentCollection holder) { //TODO:refactor + make this method private + if ( arrayHolders == null ) { + arrayHolders = new IdentityHashMap<>( INIT_COLL_SIZE ); + } arrayHolders.put( holder.getValue(), holder ); } @Override public PersistentCollection removeCollectionHolder(Object array) { - return arrayHolders.remove( array ); + return arrayHolders != null ? arrayHolders.remove( array ) : null; } @Override @@ -989,7 +1013,7 @@ public class StatefulPersistenceContext implements PersistenceContext { } else { coll = getCollectionHolder( collection ); - if ( coll == null ) { + if ( coll == null && collectionEntries != null ) { //it might be an unwrapped collection reference! //try to find a wrapper (slowish) final Iterator wrappers = collectionEntries.keyIterator(); @@ -1008,12 +1032,12 @@ public class StatefulPersistenceContext implements PersistenceContext { @Override public Object getProxy(EntityKey key) { - return proxiesByKey.get( key ); + return proxiesByKey == null ? null : proxiesByKey.get( key ); } @Override public void addProxy(EntityKey key, Object proxy) { - proxiesByKey.put( key, proxy ); + getOrInitializeProxiesByKey().put( key, proxy ); } @Override @@ -1022,11 +1046,14 @@ public class StatefulPersistenceContext implements PersistenceContext { batchFetchQueue.removeBatchLoadableEntityKey( key ); batchFetchQueue.removeSubselect( key ); } - return proxiesByKey.remove( key ); + return removeProxyByKey( key ); } @Override public HashSet getNullifiableEntityKeys() { + if ( nullifiableEntityKeys == null ) { + nullifiableEntityKeys = new HashSet<>(); + } return nullifiableEntityKeys; } @@ -1035,10 +1062,6 @@ public class StatefulPersistenceContext implements PersistenceContext { return entitiesByKey; } - public Map getProxiesByKey() { - return proxiesByKey; - } - @Override public int getNumberOfManagedEntities() { return entityEntryContext.getNumberOfManagedEntities(); @@ -1049,9 +1072,29 @@ public class StatefulPersistenceContext implements PersistenceContext { return null; } + /** + * @deprecated We should not expose this directly: the other accessors that have been created as a replacement + * have better chances of skipping initializing this map, which is a good performance improvement. + * @return the map of managed collection entries. + */ @Override + @Deprecated public Map getCollectionEntries() { - return collectionEntries; + return getOrInitializeCollectionEntries(); + } + + @Override + public void forEachCollectionEntry(BiConsumer action, boolean concurrent) { + if ( collectionEntries != null ) { + if ( concurrent ) { + for ( Map.Entry entry : IdentityMap.concurrentEntries( collectionEntries ) ) { + action.accept( entry.getKey(), entry.getValue() ); + } + } + else { + collectionEntries.forEach( action ); + } + } } @Override @@ -1163,7 +1206,7 @@ public class StatefulPersistenceContext implements PersistenceContext { final CollectionPersister collectionPersister = session.getFactory().getMetamodel().collectionPersister( collectionRole ); // try cache lookup first - final Object parent = parentsByChild.get( childEntity ); + final Object parent = getParentsByChild( childEntity ); if ( parent != null ) { final EntityEntry entityEntry = entityEntryContext.getEntityEntry( parent ); //there maybe more than one parent, filter by type @@ -1173,7 +1216,7 @@ public class StatefulPersistenceContext implements PersistenceContext { } else { // remove wrong entry - parentsByChild.remove( childEntity ); + removeChildParent( childEntity ); } } @@ -1264,6 +1307,13 @@ public class StatefulPersistenceContext implements PersistenceContext { return null; } + private Object getParentsByChild(Object childEntity) { + if ( parentsByChild != null ) { + parentsByChild.get( childEntity ); + } + return null; + } + private boolean isFoundInParent( String property, Object childEntity, @@ -1278,18 +1328,19 @@ public class StatefulPersistenceContext implements PersistenceContext { @Override public Object getIndexInOwner(String entity, String property, Object childEntity, Map mergeMap) { - final EntityPersister persister = session.getFactory().getMetamodel().entityPersister( entity ); - final CollectionPersister cp = session.getFactory().getMetamodel().collectionPersister( entity + '.' + property ); + final MetamodelImplementor metamodel = session.getFactory().getMetamodel(); + final EntityPersister persister = metamodel.entityPersister( entity ); + final CollectionPersister cp = metamodel.collectionPersister( entity + '.' + property ); // try cache lookup first - final Object parent = parentsByChild.get( childEntity ); + final Object parent = getParentsByChild( childEntity ); if ( parent != null ) { final EntityEntry entityEntry = entityEntryContext.getEntityEntry( parent ); //there maybe more than one parent, filter by type if ( persister.isSubclassEntityName( entityEntry.getEntityName() ) ) { Object index = getIndexInParent( property, childEntity, persister, cp, parent ); - if (index==null && mergeMap!=null) { + if ( index == null && mergeMap != null ) { final Object unMergedInstance = mergeMap.get( parent ); final Object unMergedChild = mergeMap.get( childEntity ); if ( unMergedInstance != null && unMergedChild != null ) { @@ -1306,7 +1357,7 @@ public class StatefulPersistenceContext implements PersistenceContext { } else { // remove wrong entry - parentsByChild.remove( childEntity ); + removeChildParent( childEntity ); } } @@ -1354,16 +1405,19 @@ public class StatefulPersistenceContext implements PersistenceContext { @Override public void addNullProperty(EntityKey ownerKey, String propertyName) { + if ( nullAssociations == null ) { + nullAssociations = new HashSet<>( INIT_COLL_SIZE ); + } nullAssociations.add( new AssociationKey( ownerKey, propertyName ) ); } @Override public boolean isPropertyNull(EntityKey ownerKey, String propertyName) { - return nullAssociations.contains( new AssociationKey( ownerKey, propertyName ) ); + return nullAssociations != null && nullAssociations.contains( new AssociationKey( ownerKey, propertyName ) ); } private void clearNullProperties() { - nullAssociations.clear(); + nullAssociations = null; } @Override @@ -1378,7 +1432,7 @@ public class StatefulPersistenceContext implements PersistenceContext { else { final EntityEntry ee = getEntry( entityOrProxy ); if ( ee == null ) { - throw new TransientObjectException("Instance was not associated with this persistence context" ); + throw new TransientObjectException( "Instance was not associated with this persistence context" ); } isReadOnly = ee.isReadOnly(); } @@ -1415,11 +1469,12 @@ public class StatefulPersistenceContext implements PersistenceContext { } private void setProxyReadOnly(HibernateProxy proxy, boolean readOnly) { - if ( proxy.getHibernateLazyInitializer().getSession() != getSession() ) { + final LazyInitializer hibernateLazyInitializer = proxy.getHibernateLazyInitializer(); + if ( hibernateLazyInitializer.getSession() != getSession() ) { throw new AssertionFailure( "Attempt to set a proxy to read-only that is associated with a different session" ); } - proxy.getHibernateLazyInitializer().setReadOnly( readOnly ); + hibernateLazyInitializer.setReadOnly( readOnly ); } private void setEntityReadOnly(Object entity, boolean readOnly) { @@ -1435,7 +1490,7 @@ public class StatefulPersistenceContext implements PersistenceContext { public void replaceDelayedEntityIdentityInsertKeys(EntityKey oldKey, Serializable generatedId) { final Object entity = entitiesByKey.remove( oldKey ); final EntityEntry oldEntry = entityEntryContext.removeEntityEntry( entity ); - parentsByChild.clear(); + this.parentsByChild = null; final EntityKey newKey = session.generateEntityKey( generatedId, oldEntry.getPersister() ); addEntity( newKey, entity ); @@ -1461,9 +1516,7 @@ public class StatefulPersistenceContext implements PersistenceContext { * @throws IOException serialization errors. */ public void serialize(ObjectOutputStream oos) throws IOException { - if ( LOG.isTraceEnabled() ) { - LOG.trace( "Serializing persistence-context" ); - } + LOG.trace( "Serializing persistence-context" ); oos.writeBoolean( defaultReadOnly ); oos.writeBoolean( hasNonReadOnlyEntities ); @@ -1477,22 +1530,32 @@ public class StatefulPersistenceContext implements PersistenceContext { oos.writeObject( entry.getValue() ); } - oos.writeInt( entitiesByUniqueKey.size() ); - if ( LOG.isTraceEnabled() ) { - LOG.trace( "Starting serialization of [" + entitiesByUniqueKey.size() + "] entitiesByUniqueKey entries" ); + if ( entitiesByUniqueKey == null ) { + oos.writeInt( 0 ); } - for ( Map.Entry entry : entitiesByUniqueKey.entrySet() ) { - entry.getKey().serialize( oos ); - oos.writeObject( entry.getValue() ); + else { + oos.writeInt( entitiesByUniqueKey.size() ); + if ( LOG.isTraceEnabled() ) { + LOG.trace( "Starting serialization of [" + entitiesByUniqueKey.size() + "] entitiesByUniqueKey entries" ); + } + for ( Map.Entry entry : entitiesByUniqueKey.entrySet() ) { + entry.getKey().serialize( oos ); + oos.writeObject( entry.getValue() ); + } } - oos.writeInt( proxiesByKey.size() ); - if ( LOG.isTraceEnabled() ) { - LOG.trace( "Starting serialization of [" + proxiesByKey.size() + "] proxiesByKey entries" ); + if ( proxiesByKey == null ) { + oos.writeInt( 0 ); } - for ( Map.Entry entry : proxiesByKey.entrySet() ) { - entry.getKey().serialize( oos ); - oos.writeObject( entry.getValue() ); + else { + oos.writeInt( proxiesByKey.size() ); + if ( LOG.isTraceEnabled() ) { + LOG.trace( "Starting serialization of [" + proxiesByKey.size() + "] proxiesByKey entries" ); + } + for ( Map.Entry entry : proxiesByKey.entrySet() ) { + entry.getKey().serialize( oos ); + oos.writeObject( entry.getValue() ); + } } oos.writeInt( entitySnapshotsByKey.size() ); @@ -1515,30 +1578,46 @@ public class StatefulPersistenceContext implements PersistenceContext { oos.writeObject( entry.getValue() ); } - oos.writeInt( collectionEntries.size() ); - if ( LOG.isTraceEnabled() ) { - LOG.trace( "Starting serialization of [" + collectionEntries.size() + "] collectionEntries entries" ); + if ( collectionEntries == null ) { + oos.writeInt( 0 ); } - for ( Map.Entry entry : collectionEntries.entrySet() ) { - oos.writeObject( entry.getKey() ); - entry.getValue().serialize( oos ); + else { + oos.writeInt( collectionEntries.size() ); + if ( LOG.isTraceEnabled() ) { + LOG.trace( "Starting serialization of [" + collectionEntries.size() + "] collectionEntries entries" ); + } + for ( Map.Entry entry : collectionEntries.entrySet() ) { + oos.writeObject( entry.getKey() ); + entry.getValue().serialize( oos ); + } } - oos.writeInt( arrayHolders.size() ); - if ( LOG.isTraceEnabled() ) { - LOG.trace( "Starting serialization of [" + arrayHolders.size() + "] arrayHolders entries" ); + if ( arrayHolders == null ) { + oos.writeInt( 0 ); } - for ( Map.Entry entry : arrayHolders.entrySet() ) { - oos.writeObject( entry.getKey() ); - oos.writeObject( entry.getValue() ); + else { + oos.writeInt( arrayHolders.size() ); + if ( LOG.isTraceEnabled() ) { + LOG.trace( "Starting serialization of [" + arrayHolders.size() + "] arrayHolders entries" ); + } + for ( Map.Entry entry : arrayHolders.entrySet() ) { + oos.writeObject( entry.getKey() ); + oos.writeObject( entry.getValue() ); + } } - oos.writeInt( nullifiableEntityKeys.size() ); - if ( LOG.isTraceEnabled() ) { - LOG.trace( "Starting serialization of [" + nullifiableEntityKeys.size() + "] nullifiableEntityKey entries" ); + if ( nullifiableEntityKeys == null ) { + oos.writeInt( 0 ); } - for ( EntityKey entry : nullifiableEntityKeys ) { - entry.serialize( oos ); + else { + final int size = nullifiableEntityKeys.size(); + if ( LOG.isTraceEnabled() ) { + LOG.trace( "Starting serialization of [" + size + "] nullifiableEntityKey entries" ); + } + oos.writeInt( size ); + for ( EntityKey entry : nullifiableEntityKeys ) { + entry.serialize( oos ); + } } } @@ -1556,9 +1635,7 @@ public class StatefulPersistenceContext implements PersistenceContext { public static StatefulPersistenceContext deserialize( ObjectInputStream ois, SessionImplementor session) throws IOException, ClassNotFoundException { - if ( LOG.isTraceEnabled() ) { - LOG.trace( "Deserializing persistence-context" ); - } + LOG.trace( "Deserializing persistence-context" ); final StatefulPersistenceContext rtn = new StatefulPersistenceContext( session ); SessionFactoryImplementor sfi = session.getFactory(); @@ -1585,30 +1662,23 @@ public class StatefulPersistenceContext implements PersistenceContext { if ( LOG.isTraceEnabled() ) { LOG.trace( "Starting deserialization of [" + count + "] entitiesByUniqueKey entries" ); } - rtn.entitiesByUniqueKey = new HashMap<>( count < INIT_COLL_SIZE ? INIT_COLL_SIZE : count ); - for ( int i = 0; i < count; i++ ) { - rtn.entitiesByUniqueKey.put( EntityUniqueKey.deserialize( ois, session ), ois.readObject() ); + if ( count != 0 ) { + rtn.entitiesByUniqueKey = new HashMap<>( count < INIT_COLL_SIZE ? INIT_COLL_SIZE : count ); + for ( int i = 0; i < count; i++ ) { + rtn.entitiesByUniqueKey.put( EntityUniqueKey.deserialize( ois, session ), ois.readObject() ); + } } count = ois.readInt(); if ( LOG.isTraceEnabled() ) { LOG.trace( "Starting deserialization of [" + count + "] proxiesByKey entries" ); } - //noinspection unchecked - rtn.proxiesByKey = new ConcurrentReferenceHashMap<>( - count < INIT_COLL_SIZE ? INIT_COLL_SIZE : count, - .75f, - 1, - ConcurrentReferenceHashMap.ReferenceType.STRONG, - ConcurrentReferenceHashMap.ReferenceType.WEAK, - null - ); for ( int i = 0; i < count; i++ ) { final EntityKey ek = EntityKey.deserialize( ois, sfi ); final Object proxy = ois.readObject(); if ( proxy instanceof HibernateProxy ) { ( (HibernateProxy) proxy ).getHibernateLazyInitializer().setSession( session ); - rtn.proxiesByKey.put( ek, proxy ); + rtn.getOrInitializeProxiesByKey().put( ek, proxy ); } else { // otherwise, the proxy was pruned during the serialization process @@ -1642,21 +1712,22 @@ public class StatefulPersistenceContext implements PersistenceContext { if ( LOG.isTraceEnabled() ) { LOG.trace( "Starting deserialization of [" + count + "] collectionEntries entries" ); } - rtn.collectionEntries = IdentityMap.instantiateSequenced( count < INIT_COLL_SIZE ? INIT_COLL_SIZE : count ); for ( int i = 0; i < count; i++ ) { final PersistentCollection pc = (PersistentCollection) ois.readObject(); final CollectionEntry ce = CollectionEntry.deserialize( ois, session ); pc.setCurrentSession( session ); - rtn.collectionEntries.put( pc, ce ); + rtn.getOrInitializeCollectionEntries().put( pc, ce ); } count = ois.readInt(); if ( LOG.isTraceEnabled() ) { LOG.trace( "Starting deserialization of [" + count + "] arrayHolders entries" ); } - rtn.arrayHolders = new IdentityHashMap<>( count < INIT_COLL_SIZE ? INIT_COLL_SIZE : count ); - for ( int i = 0; i < count; i++ ) { - rtn.arrayHolders.put( ois.readObject(), (PersistentCollection) ois.readObject() ); + if ( count != 0 ) { + rtn.arrayHolders = new IdentityHashMap<>( count < INIT_COLL_SIZE ? INIT_COLL_SIZE : count ); + for ( int i = 0; i < count; i++ ) { + rtn.arrayHolders.put( ois.readObject(), (PersistentCollection) ois.readObject() ); + } } count = ois.readInt(); @@ -1678,15 +1749,19 @@ public class StatefulPersistenceContext implements PersistenceContext { @Override public void addChildParent(Object child, Object parent) { + if ( parentsByChild == null ) { + parentsByChild = new IdentityHashMap<>( INIT_COLL_SIZE ); + } parentsByChild.put( child, parent ); } @Override public void removeChildParent(Object child) { - parentsByChild.remove( child ); + if ( parentsByChild != null ) { + parentsByChild.remove( child ); + } } - // INSERTED KEYS HANDLING ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ private HashMap> insertedKeysMap; @@ -1722,17 +1797,61 @@ public class StatefulPersistenceContext implements PersistenceContext { return false; } + @Override + public boolean containsNullifiableEntityKey(Supplier sek) { + if ( nullifiableEntityKeys == null || nullifiableEntityKeys.size() == 0 ) { + return false; + } + else { + final EntityKey entityKey = sek.get(); + return nullifiableEntityKeys.contains( entityKey ); + } + } + + @Override + public void registerNullifiableEntityKey(EntityKey key) { + if ( nullifiableEntityKeys == null ) { + nullifiableEntityKeys = new HashSet<>(); + } + this.nullifiableEntityKeys.add( key ); + } + + @Override + public boolean isNullifiableEntityKeysEmpty() { + return ( nullifiableEntityKeys == null || nullifiableEntityKeys.size() == 0 ); + } + + @Override + public int getCollectionEntriesSize() { + return collectionEntries == null ? 0 : collectionEntries.size(); + } + + @Override + public CollectionEntry removeCollectionEntry(PersistentCollection collection) { + if ( collectionEntries == null ) { + return null; + } + else { + return collectionEntries.remove( collection ); + } + } + private void cleanUpInsertedKeysAfterTransaction() { if ( insertedKeysMap != null ) { insertedKeysMap.clear(); } } - - // NATURAL ID RESOLUTION HANDLING ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - private final NaturalIdXrefDelegate naturalIdXrefDelegate = new NaturalIdXrefDelegate( this ); + private NaturalIdXrefDelegate naturalIdXrefDelegate; + + private NaturalIdXrefDelegate getNaturalIdXrefDelegate() { + if ( naturalIdXrefDelegate == null ) { + this.naturalIdXrefDelegate = new NaturalIdXrefDelegate( this ); + } + return naturalIdXrefDelegate; + } private final NaturalIdHelper naturalIdHelper = new NaturalIdHelper() { @Override @@ -1751,7 +1870,7 @@ public class StatefulPersistenceContext implements PersistenceContext { // from a single load event. The first put journal would come from the natural id resolution; // the second comes from the entity loading. In this condition, we want to avoid the multiple // 'put' stats incrementing. - final boolean justAddedLocally = naturalIdXrefDelegate.cacheNaturalIdCrossReference( persister, id, naturalIdValues ); + final boolean justAddedLocally = getNaturalIdXrefDelegate().cacheNaturalIdCrossReference( persister, id, naturalIdValues ); if ( justAddedLocally && persister.hasNaturalIdCache() ) { managedSharedCacheEntries( persister, id, naturalIdValues, null, CachedNaturalIdValueSource.LOAD ); @@ -1774,7 +1893,7 @@ public class StatefulPersistenceContext implements PersistenceContext { final Object[] naturalIdValues = extractNaturalIdValues( state, persister ); // cache - naturalIdXrefDelegate.cacheNaturalIdCrossReference( persister, id, naturalIdValues ); + getNaturalIdXrefDelegate().cacheNaturalIdCrossReference( persister, id, naturalIdValues ); } @Override @@ -1848,7 +1967,7 @@ public class StatefulPersistenceContext implements PersistenceContext { new AfterTransactionCompletionProcess() { @Override public void doAfterTransactionCompletion(boolean success, SharedSessionContractImplementor session) { - if (success) { + if ( success ) { final boolean put = naturalIdCacheAccessStrategy.afterInsert( session, naturalIdCacheKey, id ); if ( put && statistics.isStatisticsEnabled() ) { statistics.naturalIdCachePut( @@ -1914,7 +2033,9 @@ public class StatefulPersistenceContext implements PersistenceContext { break; } default: { - LOG.debug( "Unexpected CachedNaturalIdValueSource [" + source + "]" ); + if ( LOG.isDebugEnabled() ) { + LOG.debug( "Unexpected CachedNaturalIdValueSource [" + source + "]" ); + } } } } @@ -1929,7 +2050,7 @@ public class StatefulPersistenceContext implements PersistenceContext { persister = locateProperPersister( persister ); final Object[] naturalIdValues = getNaturalIdValues( state, persister ); - final Object[] localNaturalIdValues = naturalIdXrefDelegate.removeNaturalIdCrossReference( + final Object[] localNaturalIdValues = getNaturalIdXrefDelegate().removeNaturalIdCrossReference( persister, id, naturalIdValues @@ -1968,12 +2089,12 @@ public class StatefulPersistenceContext implements PersistenceContext { @Override public Object[] findCachedNaturalId(EntityPersister persister, Serializable pk) { - return naturalIdXrefDelegate.findCachedNaturalId( locateProperPersister( persister ), pk ); + return getNaturalIdXrefDelegate().findCachedNaturalId( locateProperPersister( persister ), pk ); } @Override public Serializable findCachedNaturalIdResolution(EntityPersister persister, Object[] naturalIdValues) { - return naturalIdXrefDelegate.findCachedNaturalIdResolution( locateProperPersister( persister ), naturalIdValues ); + return getNaturalIdXrefDelegate().findCachedNaturalIdResolution( locateProperPersister( persister ), naturalIdValues ); } @Override @@ -2011,7 +2132,7 @@ public class StatefulPersistenceContext implements PersistenceContext { @Override public Collection getCachedPkResolutions(EntityPersister entityPersister) { - return naturalIdXrefDelegate.getCachedPkResolutions( entityPersister ); + return getNaturalIdXrefDelegate().getCachedPkResolutions( entityPersister ); } @Override @@ -2024,6 +2145,7 @@ public class StatefulPersistenceContext implements PersistenceContext { persister = locateProperPersister( persister ); final Object[] naturalIdValuesFromCurrentObjectState = extractNaturalIdValues( entity, persister ); + final NaturalIdXrefDelegate naturalIdXrefDelegate = getNaturalIdXrefDelegate(); final boolean changed = ! naturalIdXrefDelegate.sameAsCached( persister, pk, @@ -2045,12 +2167,12 @@ public class StatefulPersistenceContext implements PersistenceContext { @Override public void cleanupFromSynchronizations() { - naturalIdXrefDelegate.unStashInvalidNaturalIdReferences(); + getNaturalIdXrefDelegate().unStashInvalidNaturalIdReferences(); } @Override public void handleEviction(Object object, EntityPersister persister, Serializable identifier) { - naturalIdXrefDelegate.removeNaturalIdCrossReference( + getNaturalIdXrefDelegate().removeNaturalIdCrossReference( persister, identifier, findCachedNaturalId( persister, identifier ) diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/JdbcCoordinatorImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/JdbcCoordinatorImpl.java index 9a4ba49a3b..0101585b92 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/JdbcCoordinatorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/JdbcCoordinatorImpl.java @@ -82,7 +82,8 @@ public class JdbcCoordinatorImpl implements JdbcCoordinator { */ public JdbcCoordinatorImpl( Connection userSuppliedConnection, - JdbcSessionOwner owner) { + JdbcSessionOwner owner, + JdbcServices jdbcServices) { this.isUserSuppliedConnection = userSuppliedConnection != null; final ResourceRegistry resourceRegistry = new ResourceRegistryStandardImpl( @@ -95,13 +96,12 @@ public class JdbcCoordinatorImpl implements JdbcCoordinator { this.logicalConnection = new LogicalConnectionManagedImpl( owner.getJdbcConnectionAccess(), owner.getJdbcSessionContext(), - resourceRegistry + resourceRegistry, + jdbcServices ); } this.owner = owner; - this.jdbcServices = owner.getJdbcSessionContext() - .getServiceRegistry() - .getService( JdbcServices.class ); + this.jdbcServices = jdbcServices; } private JdbcCoordinatorImpl( @@ -223,7 +223,7 @@ public class JdbcCoordinatorImpl implements JdbcCoordinator { @Override public ResultSetReturn getResultSetReturn() { if ( resultSetExtractor == null ) { - resultSetExtractor = new ResultSetReturnImpl( this ); + resultSetExtractor = new ResultSetReturnImpl( this, jdbcServices ); } return resultSetExtractor; } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/ResultSetReturnImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/ResultSetReturnImpl.java index 63298d5522..c39549eced 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/ResultSetReturnImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/ResultSetReturnImpl.java @@ -36,16 +36,9 @@ public class ResultSetReturnImpl implements ResultSetReturn { * * @param jdbcCoordinator The JdbcCoordinator */ - public ResultSetReturnImpl(JdbcCoordinator jdbcCoordinator) { + public ResultSetReturnImpl(JdbcCoordinator jdbcCoordinator, JdbcServices jdbcServices) { this.jdbcCoordinator = jdbcCoordinator; - - final JdbcServices jdbcServices = jdbcCoordinator.getJdbcSessionOwner() - .getJdbcSessionContext() - .getServiceRegistry() - .getService( JdbcServices.class ); - this.dialect = jdbcServices.getDialect(); - this.sqlStatementLogger = jdbcServices.getSqlStatementLogger(); this.sqlExceptionHelper = jdbcServices.getSqlExceptionHelper(); } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/query/spi/HQLQueryPlan.java b/hibernate-core/src/main/java/org/hibernate/engine/query/spi/HQLQueryPlan.java index 6b0b954c21..1ba9dbe6e0 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/query/spi/HQLQueryPlan.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/query/spi/HQLQueryPlan.java @@ -163,8 +163,8 @@ public class HQLQueryPlan implements Serializable { */ public String[] getSqlStrings() { List sqlStrings = new ArrayList<>(); - for ( int i = 0; i < translators.length; i++ ) { - sqlStrings.addAll( translators[i].collectSqlStrings() ); + for ( QueryTranslator translator : translators ) { + sqlStrings.addAll( translator.collectSqlStrings() ); } return ArrayHelper.toStringArray( sqlStrings ); } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/ActionQueue.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/ActionQueue.java index 909a7efc8e..8b7c3698f4 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/ActionQueue.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/ActionQueue.java @@ -229,13 +229,13 @@ public class ActionQueue { } public void clear() { - for ( ListProvider listProvider : EXECUTABLE_LISTS_MAP.values() ) { + EXECUTABLE_LISTS_MAP.forEach( (k,listProvider) -> { ExecutableList l = listProvider.get( this ); - if( l != null ) { + if ( l != null ) { l.clear(); } - } - if( unresolvedInsertions != null ) { + } ); + if ( unresolvedInsertions != null ) { unresolvedInsertions.clear(); } } @@ -267,7 +267,7 @@ public class ActionQueue { LOG.tracev( "Adding insert with non-nullable, transient entities; insert=[{0}], dependencies=[{1}]", insert, nonNullableTransientDependencies.toLoggableString( insert.getSession() ) ); } - if( unresolvedInsertions == null ) { + if ( unresolvedInsertions == null ) { unresolvedInsertions = new UnresolvedEntityInsertActions(); } unresolvedInsertions.addUnresolvedEntityInsertAction( insert, nonNullableTransientDependencies ); @@ -288,7 +288,7 @@ public class ActionQueue { if ( !insert.isVeto() ) { insert.makeEntityManaged(); - if( unresolvedInsertions != null ) { + if ( unresolvedInsertions != null ) { for ( AbstractEntityInsertAction resolvedAction : unresolvedInsertions.resolveDependentActions( insert.getInstance(), session ) ) { addResolvedEntityInsertAction( resolvedAction ); } @@ -390,8 +390,8 @@ public class ActionQueue { } private void registerCleanupActions(Executable executable) { - if( executable.getBeforeTransactionCompletionProcess() != null ) { - if( beforeTransactionProcesses == null ) { + if ( executable.getBeforeTransactionCompletionProcess() != null ) { + if ( beforeTransactionProcesses == null ) { beforeTransactionProcesses = new BeforeTransactionCompletionProcessQueue( session ); } beforeTransactionProcesses.register( executable.getBeforeTransactionCompletionProcess() ); @@ -399,8 +399,8 @@ public class ActionQueue { if ( session.getFactory().getSessionFactoryOptions().isQueryCacheEnabled() ) { invalidateSpaces( convertTimestampSpaces( executable.getPropertySpaces() ) ); } - if( executable.getAfterTransactionCompletionProcess() != null ) { - if( afterTransactionProcesses == null ) { + if ( executable.getAfterTransactionCompletionProcess() != null ) { + if ( afterTransactionProcesses == null ) { afterTransactionProcesses = new AfterTransactionCompletionProcessQueue( session ); } afterTransactionProcesses.register( executable.getAfterTransactionCompletionProcess() ); @@ -432,20 +432,20 @@ public class ActionQueue { * the first unresolved entity insert action. */ public void checkNoUnresolvedActionsAfterOperation() throws PropertyValueException { - if(unresolvedInsertions != null) { + if ( unresolvedInsertions != null ) { unresolvedInsertions.checkNoUnresolvedActionsAfterOperation(); } } public void registerProcess(AfterTransactionCompletionProcess process) { - if( afterTransactionProcesses == null ) { + if ( afterTransactionProcesses == null ) { afterTransactionProcesses = new AfterTransactionCompletionProcessQueue( session ); } afterTransactionProcesses.register( process ); } public void registerProcess(BeforeTransactionCompletionProcess process) { - if( beforeTransactionProcesses == null ) { + if ( beforeTransactionProcesses == null ) { beforeTransactionProcesses = new BeforeTransactionCompletionProcessQueue( session ); } beforeTransactionProcesses.register( process ); @@ -472,12 +472,12 @@ public class ActionQueue { throw new IllegalStateException( "About to execute actions, but there are unresolved entity insert actions." ); } - for ( ListProvider listProvider : EXECUTABLE_LISTS_MAP.values() ) { + EXECUTABLE_LISTS_MAP.forEach( (k,listProvider) -> { ExecutableList l = listProvider.get( this ); if ( l != null && !l.isEmpty() ) { executeActions( l ); } - } + } ); } /** @@ -493,7 +493,7 @@ public class ActionQueue { } private void prepareActions(ExecutableList queue) throws HibernateException { - if( queue == null ) { + if ( queue == null ) { return; } for ( Executable executable : queue ) { @@ -509,7 +509,7 @@ public class ActionQueue { public void afterTransactionCompletion(boolean success) { if ( !isTransactionCoordinatorShared ) { // Execute completion actions only in transaction owner (aka parent session). - if( afterTransactionProcesses != null ) { + if ( afterTransactionProcesses != null ) { afterTransactionProcesses.afterTransactionCompletion( success ); } } @@ -521,7 +521,7 @@ public class ActionQueue { public void beforeTransactionCompletion() { if ( !isTransactionCoordinatorShared ) { // Execute completion actions only in transaction owner (aka parent session). - if( beforeTransactionProcesses != null ) { + if ( beforeTransactionProcesses != null ) { beforeTransactionProcesses.beforeTransactionCompletion(); } } @@ -553,7 +553,7 @@ public class ActionQueue { return true; } } - if(unresolvedInsertions == null) { + if ( unresolvedInsertions == null ) { return false; } return areTablesToBeUpdated( unresolvedInsertions, tables ); @@ -604,14 +604,14 @@ public class ActionQueue { e.execute(); } finally { - if( e.getBeforeTransactionCompletionProcess() != null ) { - if( beforeTransactionProcesses == null ) { + if ( e.getBeforeTransactionCompletionProcess() != null ) { + if ( beforeTransactionProcesses == null ) { beforeTransactionProcesses = new BeforeTransactionCompletionProcessQueue( session ); } beforeTransactionProcesses.register( e.getBeforeTransactionCompletionProcess() ); } - if( e.getAfterTransactionCompletionProcess() != null ) { - if( afterTransactionProcesses == null ) { + if ( e.getAfterTransactionCompletionProcess() != null ) { + if ( afterTransactionProcesses == null ) { afterTransactionProcesses = new AfterTransactionCompletionProcessQueue( session ); } afterTransactionProcesses.register( e.getAfterTransactionCompletionProcess() ); @@ -657,7 +657,7 @@ public class ActionQueue { private void invalidateSpaces(String... spaces) { if ( spaces != null && spaces.length > 0 ) { for ( Serializable s : spaces ) { - if( afterTransactionProcesses == null ) { + if ( afterTransactionProcesses == null ) { afterTransactionProcesses = new AfterTransactionCompletionProcessQueue( session ); } afterTransactionProcesses.addSpaceToInvalidate( (String) s ); @@ -691,21 +691,21 @@ public class ActionQueue { } public int numberOfCollectionRemovals() { - if( collectionRemovals == null ) { + if ( collectionRemovals == null ) { return 0; } return collectionRemovals.size(); } public int numberOfCollectionUpdates() { - if( collectionUpdates == null ) { + if ( collectionUpdates == null ) { return 0; } return collectionUpdates.size(); } public int numberOfCollectionCreations() { - if( collectionCreations == null ) { + if ( collectionCreations == null ) { return 0; } return collectionCreations.size(); @@ -718,24 +718,24 @@ public class ActionQueue { } public int numberOfUpdates() { - if( updates == null ) { + if ( updates == null ) { return 0; } return updates.size(); } public int numberOfInsertions() { - if( insertions == null ) { + if ( insertions == null ) { return 0; } return insertions.size(); } public TransactionCompletionProcesses getTransactionCompletionProcesses() { - if( beforeTransactionProcesses == null ) { + if ( beforeTransactionProcesses == null ) { beforeTransactionProcesses = new BeforeTransactionCompletionProcessQueue( session ); } - if( afterTransactionProcesses == null ) { + if ( afterTransactionProcesses == null ) { afterTransactionProcesses = new AfterTransactionCompletionProcessQueue( session ); } return new TransactionCompletionProcesses( beforeTransactionProcesses, afterTransactionProcesses ); @@ -758,16 +758,16 @@ public class ActionQueue { public void sortCollectionActions() { if ( isOrderUpdatesEnabled() ) { // sort the updates by fk - if( collectionCreations != null ) { + if ( collectionCreations != null ) { collectionCreations.sort(); } - if( collectionUpdates != null ) { + if ( collectionUpdates != null ) { collectionUpdates.sort(); } - if( collectionQueuedOps != null ) { + if ( collectionQueuedOps != null ) { collectionQueuedOps.sort(); } - if( collectionRemovals != null ) { + if ( collectionRemovals != null ) { collectionRemovals.sort(); } } @@ -792,16 +792,16 @@ public class ActionQueue { } public void clearFromFlushNeededCheck(int previousCollectionRemovalSize) { - if( collectionCreations != null ) { + if ( collectionCreations != null ) { collectionCreations.clear(); } - if( collectionUpdates != null ) { + if ( collectionUpdates != null ) { collectionUpdates.clear(); } - if( collectionQueuedOps != null ) { + if ( collectionQueuedOps != null ) { collectionQueuedOps.clear(); } - if( updates != null) { + if ( updates != null) { updates.clear(); } // collection deletions are a special case since update() can add @@ -835,19 +835,19 @@ public class ActionQueue { rescuedEntity = initializer.getImplementation( session ); } } - if( deletions != null ) { + if ( deletions != null ) { for ( int i = 0; i < deletions.size(); i++ ) { EntityDeleteAction action = deletions.get( i ); - if (action.getInstance() == rescuedEntity) { + if ( action.getInstance() == rescuedEntity ) { deletions.remove( i ); return; } } } - if( orphanRemovals != null ) { + if ( orphanRemovals != null ) { for ( int i = 0; i < orphanRemovals.size(); i++ ) { EntityDeleteAction action = orphanRemovals.get( i ); - if (action.getInstance() == rescuedEntity) { + if ( action.getInstance() == rescuedEntity ) { orphanRemovals.remove( i ); return; } @@ -864,14 +864,14 @@ public class ActionQueue { */ public void serialize(ObjectOutputStream oos) throws IOException { LOG.trace( "Serializing action-queue" ); - if( unresolvedInsertions == null ) { + if ( unresolvedInsertions == null ) { unresolvedInsertions = new UnresolvedEntityInsertActions(); } unresolvedInsertions.serialize( oos ); for ( ListProvider p : EXECUTABLE_LISTS_MAP.values() ) { ExecutableList l = p.get( this ); - if( l == null ) { + if ( l == null ) { oos.writeBoolean( false ); } else { @@ -902,8 +902,8 @@ public class ActionQueue { for ( ListProvider provider : EXECUTABLE_LISTS_MAP.values() ) { ExecutableList l = provider.get( rtn ); boolean notNull = ois.readBoolean(); - if( notNull ) { - if(l == null) { + if ( notNull ) { + if ( l == null ) { l = provider.init( rtn ); } l.readExternal( ois ); @@ -1218,7 +1218,7 @@ public class ActionQueue { BatchIdentifier nextBatchIdentifier = latestBatches.get( j ); if ( batchIdentifier.hasParent( nextBatchIdentifier ) ) { - if( nextBatchIdentifier.hasParent( batchIdentifier ) ) { + if ( nextBatchIdentifier.hasParent( batchIdentifier ) ) { //cycle detected, no need to continue break sort; } @@ -1232,7 +1232,7 @@ public class ActionQueue { } sorted = true; } - while ( !sorted && iterations <= maxIterations); + while ( !sorted && iterations <= maxIterations ); if ( iterations > maxIterations ) { LOG.warn( "The batch containing " + latestBatches.size() + " statements could not be sorted after " + maxIterations + " iterations. " + diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/NamedSQLQueryDefinition.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/NamedSQLQueryDefinition.java index 42ea03bb1e..ea5fa3ecbb 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/NamedSQLQueryDefinition.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/NamedSQLQueryDefinition.java @@ -236,8 +236,8 @@ public class NamedSQLQueryDefinition extends NamedQueryDefinition { allQueryReturns[i] = this.queryReturns[i]; } - for ( int j = 0; j < queryReturnsToAdd.length; j++ ) { - allQueryReturns[i] = queryReturnsToAdd[j]; + for ( NativeSQLQueryReturn queryReturnsToAdd1 : queryReturnsToAdd ) { + allQueryReturns[i] = queryReturnsToAdd1; i++; } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistenceContext.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistenceContext.java index b3a4589aa1..44057ed85e 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistenceContext.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistenceContext.java @@ -10,6 +10,8 @@ import java.io.Serializable; import java.util.Collection; import java.util.HashSet; import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Supplier; import org.hibernate.HibernateException; import org.hibernate.LockMode; @@ -477,7 +479,9 @@ public interface PersistenceContext { /** * Retrieve the set of EntityKeys representing nullifiable references + * @deprecated Use {@link #containsNullifiableEntityKey(Supplier)} or {@link #registerNullifiableEntityKey(EntityKey)} or {@link #isNullifiableEntityKeysEmpty()} */ + @Deprecated HashSet getNullifiableEntityKeys(); /** @@ -507,9 +511,18 @@ public interface PersistenceContext { /** * Get the mapping from collection instance to collection entry + * @deprecated use {@link #removeCollectionEntry(PersistentCollection)} or {@link #getCollectionEntriesSize()}, {@link #forEachCollectionEntry(BiConsumer,boolean)}. */ + @Deprecated Map getCollectionEntries(); + /** + * Execute some action on each entry of the collectionEntries map, optionally iterating on a defensive copy. + * @param action the lambda to apply on each PersistentCollection,CollectionEntry map entry of the PersistenceContext. + * @param concurrent set this to false for improved efficiency, but that would make it illegal to make changes to the underlying collectionEntries map. + */ + void forEachCollectionEntry(BiConsumer action, boolean concurrent); + /** * Get the mapping from collection key to collection instance */ @@ -720,6 +733,42 @@ public interface PersistenceContext { */ boolean wasInsertedDuringTransaction(EntityPersister persister, Serializable id); + /** + * Checks if a certain {@link EntityKey} was registered as nullifiable on this {@link PersistenceContext}. + * + * @param sek a supplier for the EntityKey; this allows to not always needing to create the key; + * for example is the map is known to be empty there is no need to create one to check. + * @return true if the EntityKey had been registered before using {@link #registerNullifiableEntityKey(EntityKey)} + * @see #registerNullifiableEntityKey(EntityKey) + */ + boolean containsNullifiableEntityKey(Supplier sek); + + /** + * Registers an {@link EntityKey} as nullifiable on this {@link PersistenceContext}. + * @param key + */ + void registerNullifiableEntityKey(EntityKey key); + + /** + * @return true if no {@link EntityKey} was registered as nullifiable on this {@link PersistenceContext}. + * @see #registerNullifiableEntityKey(EntityKey) + */ + boolean isNullifiableEntityKeysEmpty(); + + /** + * The size of the internal map storing all collection entries. + * (The map is not exposed directly, but the size is often useful) + * @return the size + */ + int getCollectionEntriesSize(); + + /** + * Remove a {@link PersistentCollection} from the {@link PersistenceContext}. + * @param collection the collection to remove + * @return the matching {@link CollectionEntry}, if any was removed. + */ + CollectionEntry removeCollectionEntry(PersistentCollection collection); + /** * Provides centralized access to natural-id-related functionality. */ diff --git a/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/JtaPlatformInitiator.java b/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/JtaPlatformInitiator.java index c98d5a7fb7..ee9b808779 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/JtaPlatformInitiator.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/JtaPlatformInitiator.java @@ -40,12 +40,12 @@ public class JtaPlatformInitiator implements StandardServiceInitiator entry : - IdentityMap.concurrentEntries( (Map) persistenceContext.getCollectionEntries() ) ) { - entry.getValue().preFlush( entry.getKey() ); - } + persistenceContext.forEachCollectionEntry( (pc,ce) -> { + ce.preFlush( pc ); + }, true ); } /** @@ -252,78 +248,70 @@ public abstract class AbstractFlushingEventListener implements JpaBootstrapSensi private int flushCollections(final EventSource session, final PersistenceContext persistenceContext) throws HibernateException { LOG.trace( "Processing unreferenced collections" ); - final Map.Entry[] entries = IdentityMap.concurrentEntries( - (Map) persistenceContext.getCollectionEntries() - ); + final int count = persistenceContext.getCollectionEntriesSize(); - final int count = entries.length; - - for ( Map.Entry me : entries ) { - CollectionEntry ce = me.getValue(); - if ( !ce.isReached() && !ce.isIgnore() ) { - Collections.processUnreachableCollection( me.getKey(), session ); - } - } + persistenceContext.forEachCollectionEntry( + (persistentCollection, collectionEntry) -> { + if ( !collectionEntry.isReached() && !collectionEntry.isIgnore() ) { + Collections.processUnreachableCollection( persistentCollection, session ); + } + }, true ); // Schedule updates to collections: LOG.trace( "Scheduling collection removes/(re)creates/updates" ); - ActionQueue actionQueue = session.getActionQueue(); - for ( Map.Entry me : - IdentityMap.concurrentEntries( (Map) persistenceContext.getCollectionEntries() ) ) { - PersistentCollection coll = me.getKey(); - CollectionEntry ce = me.getValue(); - - if ( ce.isDorecreate() ) { - session.getInterceptor().onCollectionRecreate( coll, ce.getCurrentKey() ); - actionQueue.addAction( - new CollectionRecreateAction( - coll, - ce.getCurrentPersister(), - ce.getCurrentKey(), - session - ) - ); - } - if ( ce.isDoremove() ) { - session.getInterceptor().onCollectionRemove( coll, ce.getLoadedKey() ); - actionQueue.addAction( - new CollectionRemoveAction( - coll, - ce.getLoadedPersister(), - ce.getLoadedKey(), - ce.isSnapshotEmpty(coll), - session - ) - ); - } - if ( ce.isDoupdate() ) { - session.getInterceptor().onCollectionUpdate( coll, ce.getLoadedKey() ); - actionQueue.addAction( - new CollectionUpdateAction( - coll, - ce.getLoadedPersister(), - ce.getLoadedKey(), - ce.isSnapshotEmpty(coll), - session - ) - ); - } - - // todo : I'm not sure the !wasInitialized part should really be part of this check - if ( !coll.wasInitialized() && coll.hasQueuedOperations() ) { - actionQueue.addAction( - new QueuedOperationCollectionAction( - coll, - ce.getLoadedPersister(), - ce.getLoadedKey(), - session - ) - ); - } - - } + final ActionQueue actionQueue = session.getActionQueue(); + final Interceptor interceptor = session.getInterceptor(); + persistenceContext.forEachCollectionEntry( + (coll, ce) -> { + if ( ce.isDorecreate() ) { + interceptor.onCollectionRecreate( coll, ce.getCurrentKey() ); + actionQueue.addAction( + new CollectionRecreateAction( + coll, + ce.getCurrentPersister(), + ce.getCurrentKey(), + session + ) + ); + } + if ( ce.isDoremove() ) { + interceptor.onCollectionRemove( coll, ce.getLoadedKey() ); + actionQueue.addAction( + new CollectionRemoveAction( + coll, + ce.getLoadedPersister(), + ce.getLoadedKey(), + ce.isSnapshotEmpty( coll ), + session + ) + ); + } + if ( ce.isDoupdate() ) { + interceptor.onCollectionUpdate( coll, ce.getLoadedKey() ); + actionQueue.addAction( + new CollectionUpdateAction( + coll, + ce.getLoadedPersister(), + ce.getLoadedKey(), + ce.isSnapshotEmpty( coll ), + session + ) + ); + } + // todo : I'm not sure the !wasInitialized part should really be part of this check + if ( !coll.wasInitialized() && coll.hasQueuedOperations() ) { + actionQueue.addAction( + new QueuedOperationCollectionAction( + coll, + ce.getLoadedPersister(), + ce.getLoadedKey(), + session + ) + ); + } + }, true ); actionQueue.sortCollectionActions(); @@ -387,27 +375,25 @@ public abstract class AbstractFlushingEventListener implements JpaBootstrapSensi // the batch fetching queues should also be cleared - especially the collection batch fetching one persistenceContext.getBatchFetchQueue().clear(); - for ( Map.Entry me : IdentityMap.concurrentEntries( persistenceContext.getCollectionEntries() ) ) { - CollectionEntry collectionEntry = me.getValue(); - PersistentCollection persistentCollection = me.getKey(); - collectionEntry.postFlush(persistentCollection); - if ( collectionEntry.getLoadedPersister() == null ) { - //if the collection is dereferenced, unset its session reference and remove from the session cache - //iter.remove(); //does not work, since the entrySet is not backed by the set - persistentCollection.unsetSession( session ); - persistenceContext.getCollectionEntries() - .remove(persistentCollection); - } - else { - //otherwise recreate the mapping between the collection and its key - CollectionKey collectionKey = new CollectionKey( - collectionEntry.getLoadedPersister(), - collectionEntry.getLoadedKey() - ); - persistenceContext.getCollectionsByKey().put(collectionKey, persistentCollection); - } - } - + persistenceContext.forEachCollectionEntry( + (persistentCollection, collectionEntry) -> { + collectionEntry.postFlush( persistentCollection ); + if ( collectionEntry.getLoadedPersister() == null ) { + //if the collection is dereferenced, unset its session reference and remove from the session cache + //iter.remove(); //does not work, since the entrySet is not backed by the set + persistentCollection.unsetSession( session ); + persistenceContext.removeCollectionEntry( persistentCollection ); + } + else { + //otherwise recreate the mapping between the collection and its key + CollectionKey collectionKey = new CollectionKey( + collectionEntry.getLoadedPersister(), + collectionEntry.getLoadedKey() + ); + persistenceContext.getCollectionsByKey().put( collectionKey, persistentCollection ); + } + }, true + ); } protected void postPostFlush(SessionImplementor session) { diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultAutoFlushEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultAutoFlushEventListener.java index 365a010bee..ec90f240fb 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultAutoFlushEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultAutoFlushEventListener.java @@ -50,6 +50,7 @@ public class DefaultAutoFlushEventListener extends AbstractFlushingEventListener flushEverythingToExecutions( event ); if ( flushIsReallyNeeded( event, source ) ) { LOG.trace( "Need to execute flush" ); + event.setFlushRequired( true ); // note: performExecutions() clears all collectionXxxxtion // collections (the collection actions) in the session @@ -65,10 +66,9 @@ public class DefaultAutoFlushEventListener extends AbstractFlushingEventListener } else { LOG.trace( "Don't need to execute flush" ); + event.setFlushRequired( false ); actionQueue.clearFromFlushNeededCheck( oldSize ); } - - event.setFlushRequired( flushIsReallyNeeded( event, source ) ); } } finally { @@ -89,6 +89,6 @@ public class DefaultAutoFlushEventListener extends AbstractFlushingEventListener return !source.getHibernateFlushMode().lessThan( FlushMode.AUTO ) && source.getDontFlushFromFind() == 0 && ( persistenceContext.getNumberOfManagedEntities() > 0 || - persistenceContext.getCollectionEntries().size() > 0 ); + persistenceContext.getCollectionEntriesSize() > 0 ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultDeleteEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultDeleteEventListener.java index c1616245bc..eb09b3bf02 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultDeleteEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultDeleteEventListener.java @@ -287,7 +287,7 @@ public class DefaultDeleteEventListener implements DeleteEventListener, Callback new ForeignKeys.Nullifier( entity, true, false, session, persister ).nullifyTransientReferences( entityEntry.getDeletedState() ); new Nullability( session ).checkNullability( entityEntry.getDeletedState(), persister, Nullability.NullabilityCheckType.DELETE ); - persistenceContext.getNullifiableEntityKeys().add( key ); + persistenceContext.registerNullifiableEntityKey( key ); if ( isOrphanRemovalBeforeUpdates ) { // TODO: The removeOrphan concept is a temporary "hack" for HHH-6484. This should be removed once action/task diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultDirtyCheckEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultDirtyCheckEventListener.java index 5e42eb1cbc..7f28bb1b7a 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultDirtyCheckEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultDirtyCheckEventListener.java @@ -7,6 +7,7 @@ package org.hibernate.event.internal; import org.hibernate.HibernateException; +import org.hibernate.engine.spi.ActionQueue; import org.hibernate.event.spi.DirtyCheckEvent; import org.hibernate.event.spi.DirtyCheckEventListener; import org.hibernate.internal.CoreLogging; @@ -29,11 +30,12 @@ public class DefaultDirtyCheckEventListener extends AbstractFlushingEventListene * @throws HibernateException */ public void onDirtyCheck(DirtyCheckEvent event) throws HibernateException { - int oldSize = event.getSession().getActionQueue().numberOfCollectionRemovals(); + final ActionQueue actionQueue = event.getSession().getActionQueue(); + int oldSize = actionQueue.numberOfCollectionRemovals(); try { flushEverythingToExecutions(event); - boolean wasNeeded = event.getSession().getActionQueue().hasAnyQueuedActions(); + boolean wasNeeded = actionQueue.hasAnyQueuedActions(); if ( wasNeeded ) { LOG.debug( "Session dirty" ); } @@ -43,7 +45,7 @@ public class DefaultDirtyCheckEventListener extends AbstractFlushingEventListene event.setDirty( wasNeeded ); } finally { - event.getSession().getActionQueue().clearFromFlushNeededCheck( oldSize ); + actionQueue.clearFromFlushNeededCheck( oldSize ); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEntityEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEntityEventListener.java index 1e60d6a5d7..1f0a604902 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEntityEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEntityEventListener.java @@ -17,11 +17,14 @@ import org.hibernate.StaleObjectStateException; import org.hibernate.action.internal.DelayedPostInsertIdentifier; import org.hibernate.action.internal.EntityUpdateAction; import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.engine.internal.Nullability; import org.hibernate.engine.internal.Versioning; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.PersistenceContext; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SelfDirtinessTracker; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.Status; @@ -84,14 +87,24 @@ public class DefaultFlushEntityEventListener implements FlushEntityEventListener private void checkNaturalId( EntityPersister persister, + Object entity, EntityEntry entry, Object[] current, Object[] loaded, SessionImplementor session) { + if ( entity instanceof PersistentAttributeInterceptable ) { + final PersistentAttributeInterceptor interceptor = ( (PersistentAttributeInterceptable) entity ).$$_hibernate_getInterceptor(); + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + // EARLY EXIT!!! + // nothing to check - the entity is an un-initialized enhancement-as-proxy reference + return; + } + } + if ( persister.hasNaturalIdentifier() && entry.getStatus() != Status.READ_ONLY ) { if ( !persister.getEntityMetamodel().hasImmutableNaturalId() ) { - // SHORT-CUT: if the natural id is mutable (!immutable), no need to do the below checks // EARLY EXIT!!! + // the natural id is mutable (!immutable), no need to do the below checks return; } @@ -115,7 +128,7 @@ public class DefaultFlushEntityEventListener implements FlushEntityEventListener if ( !propertyType.isEqual( current[naturalIdentifierPropertyIndex], snapshot[i] ) ) { throw new HibernateException( String.format( - "An immutable natural identifier of entity %s was altered from %s to %s", + "An immutable natural identifier of entity %s was altered from `%s` to `%s`", persister.getEntityName(), propertyTypes[naturalIdentifierPropertyIndex].toLoggableString( snapshot[i], @@ -191,7 +204,7 @@ public class DefaultFlushEntityEventListener implements FlushEntityEventListener // grab its current state values = persister.getPropertyValues( entity ); - checkNaturalId( persister, entry, values, loadedState, session ); + checkNaturalId( persister, entity, entry, values, loadedState, session ); } return values; } diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEventListener.java index fc8889d5d2..4d32fa7bfc 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEventListener.java @@ -31,7 +31,7 @@ public class DefaultFlushEventListener extends AbstractFlushingEventListener imp final PersistenceContext persistenceContext = source.getPersistenceContextInternal(); if ( persistenceContext.getNumberOfManagedEntities() > 0 || - persistenceContext.getCollectionEntries().size() > 0 ) { + persistenceContext.getCollectionEntriesSize() > 0 ) { try { source.getEventListenerManager().flushStart(); diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultLoadEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultLoadEventListener.java index f40ca718d3..432b8650db 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultLoadEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultLoadEventListener.java @@ -300,7 +300,7 @@ public class DefaultLoadEventListener implements LoadEventListener { // entities with subclasses that define a ProxyFactory can create // a HibernateProxy so long as NO_PROXY was not specified. if ( event.getShouldUnwrapProxy() != null && event.getShouldUnwrapProxy() ) { - LOG.debugf( "Ignoring NO_PROXY for to-one association with subclasses to honor laziness" ); + LOG.debug( "Ignoring NO_PROXY for to-one association with subclasses to honor laziness" ); } return createProxy( event, persister, keyToLoad, persistenceContext ); } diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultMergeEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultMergeEventListener.java index 56326d172a..39f1f47b76 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultMergeEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultMergeEventListener.java @@ -359,7 +359,7 @@ public class DefaultMergeEventListener extends AbstractSaveEventListener impleme Object managed, EntityPersister persister, EventSource source) { - if ( incoming instanceof HibernateProxy ) { + if ( managed instanceof HibernateProxy ) { return source.getPersistenceContextInternal().unproxy( managed ); } diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultRefreshEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultRefreshEventListener.java index 0c0f2624fb..9b8b9c41b6 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultRefreshEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultRefreshEventListener.java @@ -20,15 +20,18 @@ import org.hibernate.cache.spi.access.EntityDataAccess; import org.hibernate.cache.spi.access.SoftLock; import org.hibernate.engine.internal.Cascade; import org.hibernate.engine.internal.CascadePoint; +import org.hibernate.engine.spi.ActionQueue; import org.hibernate.engine.spi.CascadingActions; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.PersistenceContext; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.RefreshEvent; import org.hibernate.event.spi.RefreshEventListener; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.metamodel.spi.MetamodelImplementor; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.pretty.MessageHelper; @@ -239,20 +242,23 @@ public class DefaultRefreshEventListener implements RefreshEventListener { private void evictCachedCollections(Type[] types, Serializable id, EventSource source) throws HibernateException { + final ActionQueue actionQueue = source.getActionQueue(); + final SessionFactoryImplementor factory = source.getFactory(); + final MetamodelImplementor metamodel = factory.getMetamodel(); for ( Type type : types ) { if ( type.isCollectionType() ) { - CollectionPersister collectionPersister = source.getFactory().getMetamodel().collectionPersister( ( (CollectionType) type ).getRole() ); + CollectionPersister collectionPersister = metamodel.collectionPersister( ( (CollectionType) type ).getRole() ); if ( collectionPersister.hasCache() ) { final CollectionDataAccess cache = collectionPersister.getCacheAccessStrategy(); final Object ck = cache.generateCacheKey( id, collectionPersister, - source.getFactory(), + factory, source.getTenantIdentifier() ); final SoftLock lock = cache.lockItem( source, ck, null ); cache.remove( source, ck ); - source.getActionQueue().registerProcess( (success, session) -> cache.unlockItem( session, ck, lock ) ); + actionQueue.registerProcess( (success, session) -> cache.unlockItem( session, ck, lock ) ); } } else if ( type.isComponentType() ) { diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/EntityCopyObserverFactoryInitiator.java b/hibernate-core/src/main/java/org/hibernate/event/internal/EntityCopyObserverFactoryInitiator.java index eb3156da3e..942f2c9f36 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/EntityCopyObserverFactoryInitiator.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/EntityCopyObserverFactoryInitiator.java @@ -35,15 +35,15 @@ public class EntityCopyObserverFactoryInitiator implements StandardServiceInitia public EntityCopyObserverFactory initiateService(final Map configurationValues, final ServiceRegistryImplementor registry) { final Object value = getConfigurationValue( configurationValues ); if ( value.equals( EntityCopyNotAllowedObserver.SHORT_NAME ) || value.equals( EntityCopyNotAllowedObserver.class.getName() ) ) { - LOG.debugf( "Configured EntityCopyObserver strategy: " + EntityCopyNotAllowedObserver.SHORT_NAME ); + LOG.debugf( "Configured EntityCopyObserver strategy: %s", EntityCopyNotAllowedObserver.SHORT_NAME ); return EntityCopyNotAllowedObserver.FACTORY_OF_SELF; } else if ( value.equals( EntityCopyAllowedObserver.SHORT_NAME ) || value.equals( EntityCopyAllowedObserver.class.getName() ) ) { - LOG.debugf( "Configured EntityCopyObserver strategy: " + EntityCopyAllowedObserver.SHORT_NAME ); + LOG.debugf( "Configured EntityCopyObserver strategy: %s", EntityCopyAllowedObserver.SHORT_NAME ); return EntityCopyAllowedObserver.FACTORY_OF_SELF; } else if ( value.equals( EntityCopyAllowedLoggedObserver.SHORT_NAME ) || value.equals( EntityCopyAllowedLoggedObserver.class.getName() ) ) { - LOG.debugf( "Configured EntityCopyObserver strategy: " + EntityCopyAllowedLoggedObserver.SHORT_NAME ); + LOG.debugf( "Configured EntityCopyObserver strategy: %s", EntityCopyAllowedLoggedObserver.SHORT_NAME ); return EntityCopyAllowedLoggedObserver.FACTORY_OF_SELF; } else { @@ -52,7 +52,7 @@ public class EntityCopyObserverFactoryInitiator implements StandardServiceInitia //and that they are indeed of the right type. EntityCopyObserver exampleInstance = registry.getService( StrategySelector.class ).resolveStrategy( EntityCopyObserver.class, value ); Class observerType = exampleInstance.getClass(); - LOG.debugf( "Configured EntityCopyObserver is a custom implementation of type " + observerType.getName() ); + LOG.debugf( "Configured EntityCopyObserver is a custom implementation of type %s", observerType.getName() ); return new EntityObserversFactoryFromClass( observerType ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/EvictVisitor.java b/hibernate-core/src/main/java/org/hibernate/event/internal/EvictVisitor.java index d47d5498e6..fa7793b1eb 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/EvictVisitor.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/EvictVisitor.java @@ -67,7 +67,7 @@ public class EvictVisitor extends AbstractVisitor { private void evictCollection(PersistentCollection collection) { final PersistenceContext persistenceContext = getSession().getPersistenceContextInternal(); - CollectionEntry ce = (CollectionEntry) persistenceContext.getCollectionEntries().remove(collection); + CollectionEntry ce = persistenceContext.removeCollectionEntry( collection ); if ( LOG.isDebugEnabled() ) { LOG.debugf( "Evicting collection: %s", diff --git a/hibernate-core/src/main/java/org/hibernate/event/service/internal/EventListenerGroupImpl.java b/hibernate-core/src/main/java/org/hibernate/event/service/internal/EventListenerGroupImpl.java index 8d5200c751..3f4cc95fd7 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/service/internal/EventListenerGroupImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/event/service/internal/EventListenerGroupImpl.java @@ -6,14 +6,19 @@ */ package org.hibernate.event.service.internal; +import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.ListIterator; +import java.util.Map; import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Supplier; import org.hibernate.event.service.spi.DuplicationStrategy; +import org.hibernate.event.service.spi.EventActionWithParameter; import org.hibernate.event.service.spi.EventListenerGroup; import org.hibernate.event.service.spi.EventListenerRegistrationException; import org.hibernate.event.service.spi.JpaBootstrapSensitive; @@ -22,13 +27,22 @@ import org.hibernate.jpa.event.spi.CallbackRegistryConsumer; /** * @author Steve Ebersole + * @author Sanne Grinovero */ class EventListenerGroupImpl implements EventListenerGroup { private EventType eventType; private final EventListenerRegistryImpl listenerRegistry; private final Set duplicationStrategies = new LinkedHashSet<>(); - private List listeners; + + // Performance: make sure iteration on this type is efficient; in particular we do not want to allocate iterators, + // not having to capture state in lambdas. + // So we keep the listeners in both a List (for convenience) and in an array (for iteration). Make sure + // their content stays in synch! + private T[] listeners = null; + + //Update both fields when making changes! + private List listenersAsList; public EventListenerGroupImpl(EventType eventType, EventListenerRegistryImpl listenerRegistry) { this.eventType = eventType; @@ -62,7 +76,8 @@ class EventListenerGroupImpl implements EventListenerGroup { @Override public int count() { - return listeners == null ? 0 : listeners.size(); + final T[] ls = listeners; + return ls == null ? 0 : ls.length; } @Override @@ -70,8 +85,38 @@ class EventListenerGroupImpl implements EventListenerGroup { if ( duplicationStrategies != null ) { duplicationStrategies.clear(); } - if ( listeners != null ) { - listeners.clear(); + listeners = null; + listenersAsList = null; + } + + @Override + public final void fireLazyEventOnEachListener(final Supplier eventSupplier, final BiConsumer actionOnEvent) { + final T[] ls = listeners; + if ( ls != null && ls.length != 0 ) { + final U event = eventSupplier.get(); + for ( T listener : ls ) { + actionOnEvent.accept( listener, event ); + } + } + } + + @Override + public final void fireEventOnEachListener(final U event, final BiConsumer actionOnEvent) { + final T[] ls = listeners; + if ( ls != null ) { + for ( T listener : ls ) { + actionOnEvent.accept( listener, event ); + } + } + } + + @Override + public void fireEventOnEachListener(final U event, final X parameter, final EventActionWithParameter actionOnEvent) { + final T[] ls = listeners; + if ( ls != null ) { + for ( T listener : ls ) { + actionOnEvent.applyEventToListener( listener, event, parameter ); + } } } @@ -82,22 +127,44 @@ class EventListenerGroupImpl implements EventListenerGroup { /** * Implementation note: should be final for performance reasons. + * @deprecated this is not the most efficient way for iterating the event listeners. + * See {@link #fireEventOnEachListener(Object, BiConsumer)} and co. for better alternatives. */ @Override + @Deprecated public final Iterable listeners() { - return listeners == null ? Collections.EMPTY_LIST : listeners; + final List ls = listenersAsList; + return ls == null ? Collections.EMPTY_LIST : ls; } @Override @SafeVarargs public final void appendListeners(T... listeners) { + internalAppendListeners( listeners ); + checkForArrayRefresh(); + } + + private void checkForArrayRefresh() { + final List list = listenersAsList; + if ( this.listeners == null ) { + T[] a = (T[]) Array.newInstance( eventType.baseListenerInterface(), list.size() ); + listeners = list.toArray( a ); + } + } + + private void internalAppendListeners(T[] listeners) { for ( T listener : listeners ) { - appendListener( listener ); + internalAppendListener( listener ); } } @Override public void appendListener(T listener) { + internalAppendListener( listener ); + checkForArrayRefresh(); + } + + private void internalAppendListener(T listener) { if ( listenerShouldGetAdded( listener ) ) { internalAppend( listener ); } @@ -106,28 +173,39 @@ class EventListenerGroupImpl implements EventListenerGroup { @Override @SafeVarargs public final void prependListeners(T... listeners) { + internalPrependListeners( listeners ); + checkForArrayRefresh(); + } + + private void internalPrependListeners(T[] listeners) { for ( T listener : listeners ) { - prependListener( listener ); + internalPreprendListener( listener ); } } @Override public void prependListener(T listener) { + internalPreprendListener( listener ); + checkForArrayRefresh(); + } + + private void internalPreprendListener(T listener) { if ( listenerShouldGetAdded( listener ) ) { internalPrepend( listener ); } } private boolean listenerShouldGetAdded(T listener) { - if ( listeners == null ) { - listeners = new ArrayList<>(); + final List ts = listenersAsList; + if ( ts == null ) { + listenersAsList = new ArrayList<>(); return true; // no need to do de-dup checks } boolean doAdd = true; strategy_loop: for ( DuplicationStrategy strategy : duplicationStrategies ) { - final ListIterator itr = listeners.listIterator(); + final ListIterator itr = ts.listIterator(); while ( itr.hasNext() ) { final T existingListener = itr.next(); if ( strategy.areMatch( listener, existingListener ) ) { @@ -157,7 +235,8 @@ class EventListenerGroupImpl implements EventListenerGroup { private void internalPrepend(T listener) { checkAgainstBaseInterface( listener ); performInjections( listener ); - listeners.add( 0, listener ); + listenersAsList.add( 0, listener ); + listeners = null; //Marks it for refreshing } private void performInjections(T listener) { @@ -183,6 +262,7 @@ class EventListenerGroupImpl implements EventListenerGroup { private void internalAppend(T listener) { checkAgainstBaseInterface( listener ); performInjections( listener ); - listeners.add( listener ); + listenersAsList.add( listener ); + listeners = null; //Marks it for refreshing } } diff --git a/hibernate-core/src/main/java/org/hibernate/event/service/spi/EventActionWithParameter.java b/hibernate-core/src/main/java/org/hibernate/event/service/spi/EventActionWithParameter.java new file mode 100644 index 0000000000..e685b5293b --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/event/service/spi/EventActionWithParameter.java @@ -0,0 +1,17 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.event.service.spi; + +import org.hibernate.Incubating; + +@Incubating +@FunctionalInterface +public interface EventActionWithParameter { + + void applyEventToListener(T eventListener, U action, X param); + +} diff --git a/hibernate-core/src/main/java/org/hibernate/event/service/spi/EventListenerGroup.java b/hibernate-core/src/main/java/org/hibernate/event/service/spi/EventListenerGroup.java index fad69d143d..213f6acb64 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/service/spi/EventListenerGroup.java +++ b/hibernate-core/src/main/java/org/hibernate/event/service/spi/EventListenerGroup.java @@ -7,7 +7,11 @@ package org.hibernate.event.service.spi; import java.io.Serializable; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Supplier; +import org.hibernate.Incubating; import org.hibernate.event.spi.EventType; /** @@ -33,6 +37,12 @@ public interface EventListenerGroup extends Serializable { public int count(); + /** + * @deprecated this is not the most efficient way for iterating the event listeners. + * See {@link #fireEventOnEachListener(Object, BiConsumer)} and its overloaded variants for better alternatives. + * @return + */ + @Deprecated public Iterable listeners(); /** @@ -54,4 +64,31 @@ public interface EventListenerGroup extends Serializable { public void clear(); + /** + * Fires an event on each registered event listener of this group. + * + * Implementation note (performance): + * the first argument is a supplier so that events can avoid allocation when no listener is registered. + * the second argument is specifically designed to avoid needing a capturing lambda. + * + * @param eventSupplier + * @param actionOnEvent + * @param the kind of event + */ + @Incubating + void fireLazyEventOnEachListener(final Supplier eventSupplier, final BiConsumer actionOnEvent); + + /** + * Similar as {@link #fireLazyEventOnEachListener(Supplier, BiConsumer)} except it doesn't use a {{@link Supplier}}: + * useful when there is no need to lazily initialize the event. + * @param event + * @param actionOnEvent + * @param the kind of event + */ + @Incubating + void fireEventOnEachListener(final U event, final BiConsumer actionOnEvent); + + @Incubating + void fireEventOnEachListener(final U event, X param, final EventActionWithParameter actionOnEvent); + } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/ParameterTranslationsImpl.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/ParameterTranslationsImpl.java index efefa8c72b..c430dc604e 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/ParameterTranslationsImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/ParameterTranslationsImpl.java @@ -48,10 +48,7 @@ public class ParameterTranslationsImpl implements ParameterTranslations { } final PositionalParameterSpecification ordinalSpecification = (PositionalParameterSpecification) specification; - final PositionalParameterInformationImpl info = ordinalParameters.computeIfAbsent( - ordinalSpecification.getLabel(), - k -> new PositionalParameterInformationImpl( k, ordinalSpecification.getExpectedType() ) - ); + final PositionalParameterInformationImpl info = getPositionalParameterInfo( ordinalParameters, ordinalSpecification ); info.addSourceLocation( i++ ); } else if ( NamedParameterSpecification.class.isInstance( specification ) ) { @@ -60,10 +57,7 @@ public class ParameterTranslationsImpl implements ParameterTranslations { } final NamedParameterSpecification namedSpecification = (NamedParameterSpecification) specification; - final NamedParameterInformationImpl info = namedParameters.computeIfAbsent( - namedSpecification.getName(), - k -> new NamedParameterInformationImpl( k, namedSpecification.getExpectedType() ) - ); + final NamedParameterInformationImpl info = getNamedParameterInfo( namedParameters, namedSpecification ); /* If a previous reference to the NamedParameter already exists with expected type null and the new @@ -96,6 +90,30 @@ public class ParameterTranslationsImpl implements ParameterTranslations { } } + private NamedParameterInformationImpl getNamedParameterInfo( + Map namedParameters, + NamedParameterSpecification namedSpecification) { + final String name = namedSpecification.getName(); + NamedParameterInformationImpl namedParameterInformation = namedParameters.get( name ); + if ( namedParameterInformation == null ) { + namedParameterInformation = new NamedParameterInformationImpl( name, namedSpecification.getExpectedType() ); + namedParameters.put( name, namedParameterInformation ); + } + return namedParameterInformation; + } + + private static PositionalParameterInformationImpl getPositionalParameterInfo( + Map ordinalParameters, + PositionalParameterSpecification ordinalSpecification) { + final Integer label = Integer.valueOf( ordinalSpecification.getLabel() ); + PositionalParameterInformationImpl positionalParameterInformation = ordinalParameters.get( label ); + if ( positionalParameterInformation == null ) { + positionalParameterInformation = new PositionalParameterInformationImpl( label, ordinalSpecification.getExpectedType() ); + ordinalParameters.put( label, positionalParameterInformation ); + } + return positionalParameterInformation; + } + @Override @SuppressWarnings("unchecked") public Map getNamedParameterInformationMap() { diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/exec/DeleteExecutor.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/exec/DeleteExecutor.java index 849e5c7991..a904220098 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/exec/DeleteExecutor.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/exec/DeleteExecutor.java @@ -17,6 +17,7 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.hql.internal.ast.HqlSqlWalker; import org.hibernate.hql.internal.ast.SqlGenerator; import org.hibernate.hql.internal.ast.tree.DeleteStatement; +import org.hibernate.metamodel.spi.MetamodelImplementor; import org.hibernate.param.ParameterSpecification; import org.hibernate.persister.collection.AbstractCollectionPersister; import org.hibernate.persister.entity.Queryable; @@ -63,15 +64,18 @@ public class DeleteExecutor extends BasicExecutor { parameterSpecifications = new ArrayList<>(); idSubselectWhere = ""; } - + + final boolean commentsEnabled = factory.getSessionFactoryOptions().isCommentsEnabled(); + final MetamodelImplementor metamodel = factory.getMetamodel(); + final boolean notSupportingTuplesInSubqueries = !dialect.supportsTuplesInSubqueries(); // If many-to-many, delete the FK row in the collection table. for ( Type type : persister.getPropertyTypes() ) { if ( type.isCollectionType() ) { final CollectionType cType = (CollectionType) type; - final AbstractCollectionPersister cPersister = (AbstractCollectionPersister) factory.getMetamodel().collectionPersister( cType.getRole() ); + final AbstractCollectionPersister cPersister = (AbstractCollectionPersister) metamodel.collectionPersister( cType.getRole() ); if ( cPersister.isManyToMany() ) { if ( persister.getIdentifierColumnNames().length > 1 - && !dialect.supportsTuplesInSubqueries() ) { + && notSupportingTuplesInSubqueries ) { LOG.warn( "This dialect is unable to cascade the delete into the many-to-many join table" + " when the entity has multiple primary keys. Either properly setup cascading on" + @@ -85,7 +89,7 @@ public class DeleteExecutor extends BasicExecutor { final String where = "(" + String.join( ", ", cPersister.getKeyColumnNames() ) + ") in " + idSubselect; final Delete delete = new Delete().setTableName( cPersister.getTableName() ).setWhere( where ); - if ( factory.getSessionFactoryOptions().isCommentsEnabled() ) { + if ( commentsEnabled ) { delete.setComment( "delete FKs in join table" ); } deletes.add( delete.toStatementString() ); diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/AbstractMapComponentNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/AbstractMapComponentNode.java index 1daa679fc2..48ee9f35e2 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/AbstractMapComponentNode.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/AbstractMapComponentNode.java @@ -11,6 +11,8 @@ import java.util.Map; import org.hibernate.hql.internal.antlr.HqlSqlTokenTypes; import org.hibernate.hql.internal.ast.util.ColumnHelper; import org.hibernate.persister.collection.QueryableCollection; +import org.hibernate.persister.entity.AbstractEntityPersister; +import org.hibernate.persister.entity.EntityPersister; import org.hibernate.type.CollectionType; import org.hibernate.type.Type; @@ -123,4 +125,19 @@ public abstract class AbstractMapComponentNode extends FromReferenceNode impleme return MapKeyEntityFromElement.buildKeyJoin( getFromElement() ); } + + @Override + public String[] getReferencedTables() { + String[] referencedTables = null; + FromElement fromElement = getFromElement(); + if ( fromElement != null ) { + EntityPersister entityPersister = fromElement.getEntityPersister(); + if ( entityPersister != null && entityPersister instanceof AbstractEntityPersister ) { + AbstractEntityPersister abstractEntityPersister = (AbstractEntityPersister) entityPersister; + referencedTables = abstractEntityPersister.getTableNames(); + } + } + return referencedTables; + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/DotNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/DotNode.java index 77c23aaa42..7b70a52b9e 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/DotNode.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/DotNode.java @@ -76,10 +76,18 @@ public class DotNode extends FromReferenceNode implements DisplayableNode, Selec * The identifier that is the name of the property. */ private String propertyName; + + /** + * The identifier that is the name of the property. In comparison with {@link #propertyName} + * it is always identical with identifier in the query, it is not changed during processing. + */ + private String originalPropertyName; + /** * The full path, to the root alias of this dot node. */ private String path; + /** * The unresolved property path relative to this dot node. */ @@ -160,6 +168,7 @@ public class DotNode extends FromReferenceNode implements DisplayableNode, Selec // Set the attributes of the property reference expression. String propName = property.getText(); propertyName = propName; + originalPropertyName = propName; // If the uresolved property path isn't set yet, just use the property name. if ( propertyPath == null ) { propertyPath = propName; @@ -692,6 +701,25 @@ public class DotNode extends FromReferenceNode implements DisplayableNode, Selec return super.getDataType(); } + @Override + public String[] getReferencedTables() { + String[] referencedTables = null; + AST firstChild = getFirstChild(); + if ( firstChild != null ) { + if ( firstChild instanceof FromReferenceNode ) { + FromReferenceNode fromReferenceNode = (FromReferenceNode) firstChild; + FromElement fromElement = fromReferenceNode.getFromElement(); + if ( fromElement != null ) { + String table = fromElement.getPropertyTableName( getOriginalPropertyName() ); + if ( table != null ) { + referencedTables = new String[] { table }; + } + } + } + } + return referencedTables; + } + public void setPropertyPath(String propertyPath) { this.propertyPath = propertyPath; } @@ -700,6 +728,14 @@ public class DotNode extends FromReferenceNode implements DisplayableNode, Selec return propertyPath; } + public String getPropertyName() { + return propertyName; + } + + public String getOriginalPropertyName() { + return originalPropertyName; + } + public FromReferenceNode getLhs() { FromReferenceNode lhs = ( (FromReferenceNode) getFirstChild() ); if ( lhs == null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElement.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElement.java index 8484c999e6..6470c33d99 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElement.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElement.java @@ -343,13 +343,10 @@ public class FromElement extends HqlSqlWalkerNode implements DisplayableNode, Pa final String[] propertyNames = getIdentifierPropertyNames(); List columns = new ArrayList<>(); final boolean inSelect = getWalker().getStatementType() == HqlSqlTokenTypes.SELECT; - for ( int i = 0; i < propertyNames.length; i++ ) { - String[] propertyNameColumns = toColumns( - table, propertyNames[i], - inSelect - ); - for ( int j = 0; j < propertyNameColumns.length; j++ ) { - columns.add( propertyNameColumns[j] ); + for ( String propertyName : propertyNames ) { + String[] propertyNameColumns = toColumns( table, propertyName, inSelect ); + for ( String propertyNameColumn : propertyNameColumns ) { + columns.add( propertyNameColumn ); } } return columns.toArray( new String[columns.size()] ); @@ -517,6 +514,10 @@ public class FromElement extends HqlSqlWalkerNode implements DisplayableNode, Pa return elementType.getPropertyType( propertyName, propertyPath ); } + public String getPropertyTableName(String propertyName) { + return elementType.getPropertyTableName( propertyName ); + } + public String[] toColumns(String tableAlias, String path, boolean inSelect) { return elementType.toColumns( tableAlias, path, inSelect ); } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElementType.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElementType.java index 9fe8f365e4..665733981e 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElementType.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElementType.java @@ -27,6 +27,7 @@ import org.hibernate.param.ParameterSpecification; import org.hibernate.persister.collection.CollectionPropertyMapping; import org.hibernate.persister.collection.CollectionPropertyNames; import org.hibernate.persister.collection.QueryableCollection; +import org.hibernate.persister.entity.AbstractEntityPersister; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.Joinable; import org.hibernate.persister.entity.PropertyMapping; @@ -372,6 +373,15 @@ class FromElementType { return queryableCollection; } + public String getPropertyTableName(String propertyName) { + checkInitialized(); + if ( this.persister != null ) { + AbstractEntityPersister aep = (AbstractEntityPersister) this.persister; + return aep.getPropertyTableName( propertyName ); + } + return null; + } + /** * Returns the type of a property, given it's name (the last part) and the full path. * diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromReferenceNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromReferenceNode.java index 7e44fabe97..8446af2aee 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromReferenceNode.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromReferenceNode.java @@ -137,4 +137,14 @@ public abstract class FromReferenceNode extends AbstractSelectExpression || getWalker().getStatementType() == HqlSqlTokenTypes.UPDATE; } + /** + * Returns table names which are referenced by this node. If the tables + * can not be determined it returns null. + * + * @return table names or null. + */ + public String[] getReferencedTables() { + return null; + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/IntoClause.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/IntoClause.java index 65a82d1724..02c38142d4 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/IntoClause.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/IntoClause.java @@ -161,8 +161,8 @@ public class IntoClause extends HqlSqlWalkerNode implements DisplayableNode { if ( componentIds == null ) { String[] propertyNames = ( (CompositeType) persister.getIdentifierType() ).getPropertyNames(); componentIds = new HashSet(); - for ( int i = 0; i < propertyNames.length; i++ ) { - componentIds.add( propertyNames[i] ); + for ( String propertyName : propertyNames ) { + componentIds.add( propertyName ); } } if ( componentIds.contains( name ) ) { @@ -194,8 +194,8 @@ public class IntoClause extends HqlSqlWalkerNode implements DisplayableNode { } private void renderColumns(String[] columnNames) { - for ( int i = 0; i < columnNames.length; i++ ) { - columnSpec += columnNames[i] + ", "; + for ( String columnName : columnNames ) { + columnSpec += columnName + ", "; } } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/ASTPrinter.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/ASTPrinter.java index a9ed6538ca..43408a478f 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/ASTPrinter.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/ASTPrinter.java @@ -10,6 +10,8 @@ import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import org.hibernate.hql.internal.ast.tree.DisplayableNode; @@ -25,7 +27,7 @@ import antlr.collections.AST; * @author Joshua Davis * @author Steve Ebersole */ -public final class ASTPrinter { +public class ASTPrinter { // This is a map: array index is the ANTLR Token ID, array value is the name of that token. // There might be gaps in the array (null values) but it's generally quite compact. @@ -103,15 +105,7 @@ public final class ASTPrinter { return; } - for ( AST parent : parents ) { - if ( parent.getNextSibling() == null ) { - - pw.print( " " ); - } - else { - pw.print( " | " ); - } - } + indentLine( parents, pw ); if ( ast.getNextSibling() == null ) { pw.print( " \\-" ); @@ -121,6 +115,7 @@ public final class ASTPrinter { } showNode( pw, ast ); + showNodeProperties( parents, pw, ast ); ArrayList newParents = new ArrayList( parents ); newParents.add( ast ); @@ -130,6 +125,17 @@ public final class ASTPrinter { newParents.clear(); } + private void indentLine(List parents, PrintWriter pw) { + for ( AST parent : parents ) { + if ( parent.getNextSibling() == null ) { + pw.print( " " ); + } + else { + pw.print( " | " ); + } + } + } + private void showNode(PrintWriter pw, AST ast) { String s = nodeToString( ast ); pw.println( s ); @@ -158,6 +164,24 @@ public final class ASTPrinter { return buf.toString(); } + private void showNodeProperties(ArrayList parents, PrintWriter pw, AST ast) { + Map nodeProperties = createNodeProperties( ast ); + ArrayList parentsAndNode = new ArrayList<>( parents ); + parentsAndNode.add( ast ); + for ( String propertyName : nodeProperties.keySet() ) { + indentLine( parentsAndNode, pw ); + pw.println( propertyToString( propertyName, nodeProperties.get( propertyName ), ast ) ); + } + } + + public LinkedHashMap createNodeProperties(AST ast) { + return new LinkedHashMap<>(); + } + + public String propertyToString(String label, Object value, AST ast) { + return String.format( "%s: %s", label, value ); + } + public static void appendEscapedMultibyteChars(String text, StringBuilder buf) { char[] chars = text.toCharArray(); for ( char aChar : chars ) { diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/ASTReferencedTablesPrinter.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/ASTReferencedTablesPrinter.java new file mode 100644 index 0000000000..1999881f1d --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/ASTReferencedTablesPrinter.java @@ -0,0 +1,72 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.hql.internal.ast.util; + +import java.util.Arrays; +import java.util.LinkedHashMap; + +import org.hibernate.hql.internal.ast.tree.DotNode; +import org.hibernate.hql.internal.ast.tree.FromElement; +import org.hibernate.hql.internal.ast.tree.FromReferenceNode; +import org.hibernate.hql.internal.ast.tree.IdentNode; +import org.hibernate.hql.internal.ast.tree.SelectClause; +import org.hibernate.persister.entity.AbstractEntityPersister; +import org.hibernate.persister.entity.EntityPersister; + +import antlr.collections.AST; + +public class ASTReferencedTablesPrinter extends ASTPrinter { + + public ASTReferencedTablesPrinter(Class tokenTypeConstants) { + super( tokenTypeConstants ); + } + + @Override + public String nodeToString(AST ast) { + if ( ast == null ) { + return "{node:null}"; + } + return ast.getClass().getSimpleName(); + } + + @Override + public LinkedHashMap createNodeProperties(AST node) { + LinkedHashMap props = new LinkedHashMap<>(); + if ( node instanceof FromReferenceNode ) { + FromReferenceNode frn = (FromReferenceNode) node; + FromElement fromElement = frn.getFromElement(); + EntityPersister entityPersister = fromElement != null ? fromElement.getEntityPersister() : null; + String entityPersisterStr = entityPersister != null ? entityPersister.toString() : null; + props.put( "persister", entityPersisterStr ); + String referencedTablesStr = Arrays.toString( frn.getReferencedTables() ); + props.put( "referencedTables", referencedTablesStr ); + } + if ( node instanceof DotNode ) { + DotNode dn = (DotNode) node; + props.put( "path", dn.getPath() ); + props.put( "originalPropertyName", dn.getOriginalPropertyName() ); + } + if ( node instanceof IdentNode ) { + IdentNode in = (IdentNode) node; + props.put( "originalText", in.getOriginalText() ); + } + if ( node instanceof SelectClause ) { + SelectClause sc = (SelectClause) node; + for ( Object element : sc.getFromElementsForLoad() ) { + FromElement fromElement = (FromElement) element; + EntityPersister entityPersister = fromElement.getEntityPersister(); + if ( entityPersister != null && entityPersister instanceof AbstractEntityPersister ) { + AbstractEntityPersister aep = (AbstractEntityPersister) entityPersister; + String entityClass = aep.getMappedClass().getSimpleName(); + String tables = Arrays.toString( aep.getTableNames() ); + props.put( String.format( "referencedTables(entity %s)", entityClass ), tables ); + } + } + } + return props; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/JoinProcessor.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/JoinProcessor.java index 0196149407..f3361a8e5e 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/JoinProcessor.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/JoinProcessor.java @@ -8,14 +8,17 @@ package org.hibernate.hql.internal.ast.util; import java.util.ArrayList; import java.util.Collection; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.ListIterator; +import java.util.Set; import java.util.StringTokenizer; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.hibernate.AssertionFailure; +import org.hibernate.cfg.AvailableSettings; import org.hibernate.dialect.Dialect; import org.hibernate.engine.internal.JoinSequence; import org.hibernate.engine.spi.LoadQueryInfluencers; @@ -24,6 +27,7 @@ import org.hibernate.hql.internal.ast.HqlSqlWalker; import org.hibernate.hql.internal.ast.tree.DotNode; import org.hibernate.hql.internal.ast.tree.FromClause; import org.hibernate.hql.internal.ast.tree.FromElement; +import org.hibernate.hql.internal.ast.tree.FromReferenceNode; import org.hibernate.hql.internal.ast.tree.ImpliedFromElement; import org.hibernate.hql.internal.ast.tree.ParameterContainer; import org.hibernate.hql.internal.ast.tree.QueryNode; @@ -33,11 +37,16 @@ import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.FilterImpl; import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.collections.ArrayHelper; +import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.param.DynamicFilterParameterSpecification; +import org.hibernate.persister.entity.AbstractEntityPersister; +import org.hibernate.persister.entity.EntityPersister; import org.hibernate.sql.JoinFragment; import org.hibernate.sql.JoinType; import org.hibernate.type.Type; +import antlr.collections.AST; + /** * Performs the post-processing of the join information gathered during semantic analysis. * The join generating classes are complex, this encapsulates some of the JoinSequence-related @@ -94,9 +103,86 @@ public class JoinProcessor implements SqlTokenTypes { } } + private List findAllNodes(AST node, Class clazz) { + ArrayList found = new ArrayList<>(); + doFindAllNodes( node, clazz, found ); + return found; + } + + private void doFindAllNodes(AST node, Class clazz, List found) { + if ( clazz.isAssignableFrom( node.getClass() ) ) { + found.add( (T) node ); + } + if ( node.getFirstChild() != null ) { + doFindAllNodes( node.getFirstChild(), clazz, found ); + } + if ( node.getNextSibling() != null ) { + doFindAllNodes( node.getNextSibling(), clazz, found ); + } + } + + private Set findQueryReferencedTables(QueryNode query) { + if ( !walker.getSessionFactoryHelper() + .getFactory() + .getSessionFactoryOptions() + .isOmitJoinOfSuperclassTablesEnabled() ) { + if ( LOG.isDebugEnabled() ) { + LOG.debug( String.format( + "Finding of query referenced tables is skipped because the feature is disabled. See %s", + AvailableSettings.OMIT_JOIN_OF_SUPERCLASS_TABLES + ) ); + } + return null; + } + + if ( CollectionHelper.isNotEmpty( walker.getEnabledFilters() ) ) { + LOG.debug( "Finding of query referenced tables is skipped because filters are enabled." ); + return null; + } + + if ( LOG.isDebugEnabled() ) { + LOG.debug( TokenPrinters.REFERENCED_TABLES_PRINTER.showAsString( + query, + "Tables referenced from query nodes:" + ) ); + } + + Set result = new HashSet<>(); + + // Find tables referenced by FromReferenceNodes + List fromReferenceNodes = findAllNodes( query, FromReferenceNode.class ); + for ( FromReferenceNode node : fromReferenceNodes ) { + String[] tables = node.getReferencedTables(); + if ( tables != null ) { + for ( String table : tables ) { + result.add( table ); + } + } + } + + // Find tables referenced by fromElementsForLoad + if ( query.getSelectClause() != null ) { + for ( Object element : query.getSelectClause().getFromElementsForLoad() ) { + FromElement fromElement = (FromElement) element; + EntityPersister entityPersister = fromElement.getEntityPersister(); + if ( entityPersister != null && entityPersister instanceof AbstractEntityPersister ) { + AbstractEntityPersister aep = (AbstractEntityPersister) entityPersister; + String[] tables = aep.getTableNames(); + for ( String table : tables ) { + result.add( table ); + } + } + } + } + + return result; + } + public void processJoins(QueryNode query) { final FromClause fromClause = query.getFromClause(); + Set queryReferencedTables = findQueryReferencedTables( query ); + final List fromElements; if ( DotNode.useThetaStyleImplicitJoins ) { // for regression testing against output from the old parser... @@ -136,6 +222,7 @@ public class JoinProcessor implements SqlTokenTypes { while ( iter.hasNext() ) { final FromElement fromElement = (FromElement) iter.next(); JoinSequence join = fromElement.getJoinSequence(); + join.setQueryReferencedTables( queryReferencedTables ); join.setSelector( new JoinSequence.Selector() { public boolean includeSubclasses(String alias) { diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/TokenPrinters.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/TokenPrinters.java index 66a03adb3b..be77713515 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/TokenPrinters.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/TokenPrinters.java @@ -21,4 +21,6 @@ public interface TokenPrinters { ASTPrinter ORDERBY_FRAGMENT_PRINTER = new ASTPrinter( GeneratedOrderByFragmentRendererTokenTypes.class ); + ASTPrinter REFERENCED_TABLES_PRINTER = new ASTReferencedTablesPrinter( SqlTokenTypes.class ); + } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/TableBasedUpdateHandlerImpl.java b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/TableBasedUpdateHandlerImpl.java index 63f88f40e4..6b47360d04 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/TableBasedUpdateHandlerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/TableBasedUpdateHandlerImpl.java @@ -143,8 +143,9 @@ public class TableBasedUpdateHandlerImpl ps = session.getJdbcCoordinator().getStatementPreparer().prepareStatement( updates[i], false ); if ( assignmentParameterSpecifications[i] != null ) { int position = 1; // jdbc params are 1-based - for ( int x = 0; x < assignmentParameterSpecifications[i].length; x++ ) { - position += assignmentParameterSpecifications[i][x].bind( ps, queryParameters, session, position ); + for ( ParameterSpecification assignmentParameterSpecification : assignmentParameterSpecifications[i] ) { + position += assignmentParameterSpecification + .bind( ps, queryParameters, session, position ); } handleAddedParametersOnUpdate( ps, session, position ); } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/cte/CteValuesListUpdateHandlerImpl.java b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/cte/CteValuesListUpdateHandlerImpl.java index 532440edb8..7fcc813233 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/cte/CteValuesListUpdateHandlerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/cte/CteValuesListUpdateHandlerImpl.java @@ -112,8 +112,8 @@ public class CteValuesListUpdateHandlerImpl } } if ( assignmentParameterSpecifications[i] != null ) { - for ( int x = 0; x < assignmentParameterSpecifications[i].length; x++ ) { - position += assignmentParameterSpecifications[i][x] + for ( ParameterSpecification assignmentParameterSpecification : assignmentParameterSpecifications[i] ) { + position += assignmentParameterSpecification .bind( ps, queryParameters, session, position ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/inline/AbstractInlineIdsUpdateHandlerImpl.java b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/inline/AbstractInlineIdsUpdateHandlerImpl.java index 5086fb132d..8db4752a96 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/inline/AbstractInlineIdsUpdateHandlerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/inline/AbstractInlineIdsUpdateHandlerImpl.java @@ -103,8 +103,8 @@ public abstract class AbstractInlineIdsUpdateHandlerImpl .prepareStatement( update, false )) { int position = 1; // jdbc params are 1-based if ( assignmentParameterSpecifications[i] != null ) { - for ( int x = 0; x < assignmentParameterSpecifications[i].length; x++ ) { - position += assignmentParameterSpecifications[i][x] + for ( ParameterSpecification assignmentParameterSpecification : assignmentParameterSpecifications[i] ) { + position += assignmentParameterSpecification .bind( ps, queryParameters, session, position ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java index bf37e43d55..d89aeb710b 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java @@ -32,20 +32,16 @@ import org.hibernate.HibernateException; import org.hibernate.Interceptor; import org.hibernate.LockMode; import org.hibernate.MultiTenancyStrategy; +import org.hibernate.SessionEventListener; import org.hibernate.SessionException; import org.hibernate.Transaction; -import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.boot.registry.classloading.spi.ClassLoadingException; import org.hibernate.cache.spi.CacheTransactionSynchronization; -import org.hibernate.cfg.Environment; -import org.hibernate.dialect.Dialect; import org.hibernate.engine.ResultSetMappingDefinition; import org.hibernate.engine.internal.SessionEventListenerManagerImpl; import org.hibernate.engine.jdbc.LobCreationContext; import org.hibernate.engine.jdbc.LobCreator; -import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; -import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider; import org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl; import org.hibernate.engine.jdbc.spi.JdbcCoordinator; import org.hibernate.engine.jdbc.spi.JdbcServices; @@ -116,6 +112,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont private transient SessionFactoryImpl factory; private final String tenantIdentifier; + protected transient FastSessionServices fastSessionServices; private UUID sessionIdentifier; private transient JdbcConnectionAccess jdbcConnectionAccess; @@ -138,12 +135,10 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont protected boolean closed; protected boolean waitingForAutoClose; - private transient boolean disallowOutOfTransactionUpdateOperations; // transient & non-final for Serialization purposes - ugh - private transient SessionEventListenerManagerImpl sessionEventsManager = new SessionEventListenerManagerImpl(); + private transient SessionEventListenerManagerImpl sessionEventsManager; private transient EntityNameResolver entityNameResolver; - private transient Boolean useStreamForLobBinding; private Integer jdbcBatchSize; @@ -154,8 +149,9 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont public AbstractSharedSessionContract(SessionFactoryImpl factory, SessionCreationOptions options) { this.factory = factory; + this.fastSessionServices = factory.getFastSessionServices(); this.cacheTransactionSync = factory.getCache().getRegionFactory().createTransactionContext( this ); - this.disallowOutOfTransactionUpdateOperations = !factory.getSessionFactoryOptions().isAllowOutOfTransactionUpdateOperations(); + this.flushMode = options.getInitialSessionFlushMode(); @@ -173,9 +169,16 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont this.interceptor = interpret( options.getInterceptor() ); this.jdbcTimeZone = options.getJdbcTimeZone(); + final List customSessionEventListener = options.getCustomSessionEventListener(); + if ( customSessionEventListener == null ) { + sessionEventsManager = new SessionEventListenerManagerImpl( fastSessionServices.defaultSessionEventListeners.buildBaseline() ); + } + else { + sessionEventsManager = new SessionEventListenerManagerImpl( customSessionEventListener.toArray( new SessionEventListener[0] ) ); + } final StatementInspector statementInspector = interpret( options.getStatementInspector() ); - this.jdbcSessionContext = new JdbcSessionContextImpl( this, statementInspector ); + this.jdbcSessionContext = new JdbcSessionContextImpl( this, statementInspector, fastSessionServices ); this.entityNameResolver = new CoordinatingEntityNameResolver( factory, interceptor ); @@ -212,11 +215,8 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont else { this.isTransactionCoordinatorShared = false; this.autoJoinTransactions = options.shouldAutoJoinTransactions(); - - this.jdbcCoordinator = new JdbcCoordinatorImpl( options.getConnection(), this ); - this.transactionCoordinator = factory.getServiceRegistry() - .getService( TransactionCoordinatorBuilder.class ) - .buildTransactionCoordinator( jdbcCoordinator, this ); + this.jdbcCoordinator = new JdbcCoordinatorImpl( options.getConnection(), this, fastSessionServices.jdbcServices ); + this.transactionCoordinator = fastSessionServices.transactionCoordinatorBuilder.buildTransactionCoordinator( jdbcCoordinator, this ); } } @@ -405,14 +405,14 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont @Override public void checkTransactionNeededForUpdateOperation(String exceptionMessage) { - if ( disallowOutOfTransactionUpdateOperations && !isTransactionInProgress() ) { + if ( fastSessionServices.disallowOutOfTransactionUpdateOperations && !isTransactionInProgress() ) { throw new TransactionRequiredException( exceptionMessage ); } } @Override public Transaction getTransaction() throws HibernateException { - if ( !isTransactionAccessible() ) { + if ( ! fastSessionServices.isJtaTransactionAccessible ) { throw new IllegalStateException( "Transaction is not accessible when using JTA with JPA-compliant transaction access enabled" ); @@ -420,17 +420,6 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont return accessTransaction(); } - protected boolean isTransactionAccessible() { - // JPA requires that access not be provided to the transaction when using JTA. - // This is overridden when SessionFactoryOptions isJtaTransactionAccessEnabled() is true. - if ( getFactory().getSessionFactoryOptions().getJpaCompliance().isJpaTransactionComplianceEnabled() && - getTransactionCoordinator().getTransactionCoordinatorBuilder().isJta() && - !getFactory().getSessionFactoryOptions().isJtaTransactionAccessEnabled() ) { - return false; - } - return true; - } - @Override public Transaction accessTransaction() { if ( this.currentHibernateTransaction == null ) { @@ -439,7 +428,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont this ); } - if ( !isClosed() || (waitingForAutoClose && factory.isOpen()) ) { + if ( !isClosed() || ( waitingForAutoClose && factory.isOpen() ) ) { getTransactionCoordinator().pulse(); } return this.currentHibernateTransaction; @@ -512,17 +501,17 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont public JdbcConnectionAccess getJdbcConnectionAccess() { // See class-level JavaDocs for a discussion of the concurrent-access safety of this method if ( jdbcConnectionAccess == null ) { - if ( !factory.getSettings().getMultiTenancyStrategy().requiresMultiTenantConnectionProvider() ) { + if ( ! fastSessionServices.requiresMultiTenantConnectionProvider ) { jdbcConnectionAccess = new NonContextualJdbcConnectionAccess( getEventListenerManager(), - factory.getServiceRegistry().getService( ConnectionProvider.class ) + fastSessionServices.connectionProvider ); } else { jdbcConnectionAccess = new ContextualJdbcConnectionAccess( getTenantIdentifier(), getEventListenerManager(), - factory.getServiceRegistry().getService( MultiTenantConnectionProvider.class ) + fastSessionServices.multiTenantConnectionProvider ); } } @@ -536,11 +525,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont @Override public boolean useStreamForLobBinding() { - if ( useStreamForLobBinding == null ) { - useStreamForLobBinding = Environment.useStreamsForBinary() - || getJdbcServices().getJdbcEnvironment().getDialect().useInputStreamToInsertBlob(); - } - return useStreamForLobBinding; + return fastSessionServices.useStreamForLobBinding; } @Override @@ -567,13 +552,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont @Override public SqlTypeDescriptor remapSqlTypeDescriptor(SqlTypeDescriptor sqlTypeDescriptor) { - if ( !sqlTypeDescriptor.canBeRemapped() ) { - return sqlTypeDescriptor; - } - - final Dialect dialect = getJdbcServices().getJdbcEnvironment().getDialect(); - final SqlTypeDescriptor remapped = dialect.remapSqlTypeDescriptor( sqlTypeDescriptor ); - return remapped == null ? sqlTypeDescriptor : remapped; + return fastSessionServices.remapSqlTypeDescriptor( sqlTypeDescriptor ); } @Override @@ -928,7 +907,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont @SuppressWarnings({"WeakerAccess", "unchecked"}) protected NativeQueryImplementor createNativeQuery(NamedSQLQueryDefinition queryDefinition, Class resultType) { - if ( resultType != null && !Tuple.class.equals(resultType)) { + if ( resultType != null && !Tuple.class.equals( resultType ) ) { resultClassChecking( resultType, queryDefinition ); } @@ -937,8 +916,8 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont this, factory.getQueryPlanCache().getSQLParameterMetadata( queryDefinition.getQueryString(), false ) ); - if (Tuple.class.equals(resultType)) { - query.setResultTransformer(new NativeQueryTupleTransformer()); + if ( Tuple.class.equals( resultType ) ) { + query.setResultTransformer( new NativeQueryTupleTransformer() ); } query.setHibernateFlushMode( queryDefinition.getFlushMode() ); query.setComment( queryDefinition.getComment() != null ? queryDefinition.getComment() : queryDefinition.getName() ); @@ -976,7 +955,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont final Class actualReturnedClass; final String entityClassName = ( (NativeSQLQueryRootReturn) nativeSQLQueryReturn ).getReturnEntityName(); try { - actualReturnedClass = getFactory().getServiceRegistry().getService( ClassLoaderService.class ).classForName( entityClassName ); + actualReturnedClass = fastSessionServices.classLoaderService.classForName( entityClassName ); } catch ( ClassLoadingException e ) { throw new AssertionFailure( @@ -1219,14 +1198,15 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Step 1 :: read back non-transient state... ois.defaultReadObject(); - sessionEventsManager = new SessionEventListenerManagerImpl(); // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Step 2 :: read back transient state... // -- see above factory = SessionFactoryImpl.deserialize( ois ); - jdbcSessionContext = new JdbcSessionContextImpl( this, (StatementInspector) ois.readObject() ); + fastSessionServices = factory.getFastSessionServices(); + sessionEventsManager = new SessionEventListenerManagerImpl( fastSessionServices.defaultSessionEventListeners.buildBaseline() ); + jdbcSessionContext = new JdbcSessionContextImpl( this, (StatementInspector) ois.readObject(), fastSessionServices ); jdbcCoordinator = JdbcCoordinatorImpl.deserialize( ois, this ); cacheTransactionSync = factory.getCache().getRegionFactory().createTransactionContext( this ); @@ -1236,7 +1216,6 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont .buildTransactionCoordinator( jdbcCoordinator, this ); entityNameResolver = new CoordinatingEntityNameResolver( factory, interceptor ); - this.disallowOutOfTransactionUpdateOperations = !getFactory().getSessionFactoryOptions().isAllowOutOfTransactionUpdateOperations(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java b/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java index 99d4447451..55e392a429 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java @@ -15,6 +15,7 @@ import java.sql.SQLException; import java.sql.SQLWarning; import java.util.Hashtable; import java.util.Properties; +import java.util.ServiceConfigurationError; import java.util.Set; import javax.naming.NameNotFoundException; import javax.naming.NamingException; @@ -1871,4 +1872,8 @@ public interface CoreMessageLogger extends BasicLogger { @Message(value = "Multiple configuration properties defined to create schema. Choose at most one among 'javax.persistence.create-database-schemas', 'hibernate.hbm2ddl.create_namespaces', 'hibernate.hbm2dll.create_namespaces' (this last being deprecated).", id = 504) void multipleSchemaCreationSettingsDefined(); + @LogMessage(level = WARN) + @Message(value = "Ignoring ServiceConfigurationError caught while trying to instantiate service '%s'.", id = 505) + void ignoringServiceConfigurationError(Class serviceContract, @Cause ServiceConfigurationError error); + } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java b/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java new file mode 100644 index 0000000000..0557705813 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java @@ -0,0 +1,280 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.internal; + +import org.hibernate.CacheMode; +import org.hibernate.FlushMode; +import org.hibernate.LockOptions; +import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; +import org.hibernate.boot.spi.SessionFactoryOptions; +import org.hibernate.cfg.BaselineSessionEventsListenerBuilder; +import org.hibernate.cfg.Environment; +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; +import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider; +import org.hibernate.engine.jdbc.spi.ConnectionObserver; +import org.hibernate.engine.jdbc.spi.JdbcServices; +import org.hibernate.event.service.spi.EventListenerGroup; +import org.hibernate.event.service.spi.EventListenerRegistry; +import org.hibernate.event.spi.AutoFlushEventListener; +import org.hibernate.event.spi.ClearEventListener; +import org.hibernate.event.spi.DeleteEventListener; +import org.hibernate.event.spi.DirtyCheckEventListener; +import org.hibernate.event.spi.EventType; +import org.hibernate.event.spi.EvictEventListener; +import org.hibernate.event.spi.FlushEventListener; +import org.hibernate.event.spi.InitializeCollectionEventListener; +import org.hibernate.event.spi.LoadEventListener; +import org.hibernate.event.spi.LockEventListener; +import org.hibernate.event.spi.MergeEventListener; +import org.hibernate.event.spi.PersistEventListener; +import org.hibernate.event.spi.RefreshEventListener; +import org.hibernate.event.spi.ReplicateEventListener; +import org.hibernate.event.spi.ResolveNaturalIdEventListener; +import org.hibernate.event.spi.SaveOrUpdateEventListener; +import org.hibernate.jpa.AvailableSettings; +import org.hibernate.jpa.QueryHints; +import org.hibernate.jpa.internal.util.CacheModeHelper; +import org.hibernate.jpa.internal.util.LockOptionsHelper; +import org.hibernate.resource.transaction.spi.TransactionCoordinatorBuilder; +import org.hibernate.service.spi.ServiceRegistryImplementor; +import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import javax.persistence.CacheRetrieveMode; +import javax.persistence.CacheStoreMode; +import javax.persistence.PessimisticLockScope; + +import static org.hibernate.cfg.AvailableSettings.JPA_LOCK_SCOPE; +import static org.hibernate.cfg.AvailableSettings.JPA_LOCK_TIMEOUT; +import static org.hibernate.cfg.AvailableSettings.JPA_SHARED_CACHE_RETRIEVE_MODE; +import static org.hibernate.cfg.AvailableSettings.JPA_SHARED_CACHE_STORE_MODE; + +/** + * Internal component. + * + * Collects any components that any Session implementation will likely need + * for faster access and reduced allocations. + * Conceptually this acts as an immutable caching intermediary between Session + * and SessionFactory. + * Designed to be immutable, and shared across Session instances. + * + * Assumes to be created infrequently, possibly only once per SessionFactory. + * + * If the Session is requiring to retrieve (or compute) anything from the SessionFactory, + * and this computation would result in the same outcome for any Session created on + * this same SessionFactory, then it belongs in a final field of this class. + * + * Finally, consider also limiting the size of each Session: some fields could be good + * candidates to be replaced with access via this object. + * + * @author Sanne Grinovero + */ +final class FastSessionServices { + + /** + * Default session properties + */ + final Map defaultSessionProperties; + + // All session events need to be iterated frequently: + final EventListenerGroup eventListenerGroup_AUTO_FLUSH; + final EventListenerGroup eventListenerGroup_CLEAR; + final EventListenerGroup eventListenerGroup_DELETE; + final EventListenerGroup eventListenerGroup_DIRTY_CHECK; + final EventListenerGroup eventListenerGroup_EVICT; + final EventListenerGroup eventListenerGroup_FLUSH; + final EventListenerGroup eventListenerGroup_INIT_COLLECTION; + final EventListenerGroup eventListenerGroup_LOAD; + final EventListenerGroup eventListenerGroup_LOCK; + final EventListenerGroup eventListenerGroup_MERGE; + final EventListenerGroup eventListenerGroup_PERSIST; + final EventListenerGroup eventListenerGroup_PERSIST_ONFLUSH; + final EventListenerGroup eventListenerGroup_REFRESH; + final EventListenerGroup eventListenerGroup_REPLICATE; + final EventListenerGroup eventListenerGroup_RESOLVE_NATURAL_ID; + final EventListenerGroup eventListenerGroup_SAVE; + final EventListenerGroup eventListenerGroup_SAVE_UPDATE; + final EventListenerGroup eventListenerGroup_UPDATE; + + //Intentionally Package private: + final boolean disallowOutOfTransactionUpdateOperations; + final boolean useStreamForLobBinding; + final boolean requiresMultiTenantConnectionProvider; + final ConnectionProvider connectionProvider; + final MultiTenantConnectionProvider multiTenantConnectionProvider; + final ClassLoaderService classLoaderService; + final TransactionCoordinatorBuilder transactionCoordinatorBuilder; + final JdbcServices jdbcServices; + final boolean isJtaTransactionAccessible; + final CacheMode initialSessionCacheMode; + final boolean discardOnClose; + final BaselineSessionEventsListenerBuilder defaultSessionEventListeners; + final LockOptions defaultLockOptions; + + //Private fields: + private final Dialect dialect; + private final CacheStoreMode defaultCacheStoreMode; + private final CacheRetrieveMode defaultCacheRetrieveMode; + private final ConnectionObserverStatsBridge defaultJdbcObservers; + + FastSessionServices(SessionFactoryImpl sf) { + Objects.requireNonNull( sf ); + final ServiceRegistryImplementor sr = sf.getServiceRegistry(); + final JdbcServices jdbcServices = sf.getJdbcServices(); + final SessionFactoryOptions sessionFactoryOptions = sf.getSessionFactoryOptions(); + + // Pre-compute all iterators on Event listeners: + final EventListenerRegistry eventListenerRegistry = sr.getService( EventListenerRegistry.class ); + this.eventListenerGroup_AUTO_FLUSH = listeners( eventListenerRegistry, EventType.AUTO_FLUSH ); + this.eventListenerGroup_CLEAR = listeners( eventListenerRegistry, EventType.CLEAR ); + this.eventListenerGroup_DELETE = listeners( eventListenerRegistry, EventType.DELETE ); + this.eventListenerGroup_DIRTY_CHECK = listeners( eventListenerRegistry, EventType.DIRTY_CHECK ); + this.eventListenerGroup_EVICT = listeners( eventListenerRegistry, EventType.EVICT ); + this.eventListenerGroup_FLUSH = listeners( eventListenerRegistry, EventType.FLUSH ); + this.eventListenerGroup_INIT_COLLECTION = listeners( eventListenerRegistry, EventType.INIT_COLLECTION ); + this.eventListenerGroup_LOAD = listeners( eventListenerRegistry, EventType.LOAD ); + this.eventListenerGroup_LOCK = listeners( eventListenerRegistry, EventType.LOCK ); + this.eventListenerGroup_MERGE = listeners( eventListenerRegistry, EventType.MERGE ); + this.eventListenerGroup_PERSIST = listeners( eventListenerRegistry, EventType.PERSIST ); + this.eventListenerGroup_PERSIST_ONFLUSH = listeners( eventListenerRegistry, EventType.PERSIST_ONFLUSH ); + this.eventListenerGroup_REFRESH = listeners( eventListenerRegistry, EventType.REFRESH ); + this.eventListenerGroup_REPLICATE = listeners( eventListenerRegistry, EventType.REPLICATE ); + this.eventListenerGroup_RESOLVE_NATURAL_ID = listeners( eventListenerRegistry, EventType.RESOLVE_NATURAL_ID ); + this.eventListenerGroup_SAVE = listeners( eventListenerRegistry, EventType.SAVE ); + this.eventListenerGroup_SAVE_UPDATE = listeners( eventListenerRegistry, EventType.SAVE_UPDATE ); + this.eventListenerGroup_UPDATE = listeners( eventListenerRegistry, EventType.UPDATE ); + + //Other highly useful constants: + this.dialect = jdbcServices.getJdbcEnvironment().getDialect(); + this.disallowOutOfTransactionUpdateOperations = !sessionFactoryOptions.isAllowOutOfTransactionUpdateOperations(); + this.useStreamForLobBinding = Environment.useStreamsForBinary() || dialect.useInputStreamToInsertBlob(); + this.requiresMultiTenantConnectionProvider = sf.getSettings().getMultiTenancyStrategy().requiresMultiTenantConnectionProvider(); + + //Some "hot" services: + this.connectionProvider = requiresMultiTenantConnectionProvider ? null : sr.getService( ConnectionProvider.class ); + this.multiTenantConnectionProvider = requiresMultiTenantConnectionProvider ? sr.getService( MultiTenantConnectionProvider.class ) : null; + this.classLoaderService = sr.getService( ClassLoaderService.class ); + this.transactionCoordinatorBuilder = sr.getService( TransactionCoordinatorBuilder.class ); + this.jdbcServices = sr.getService( JdbcServices.class ); + + this.isJtaTransactionAccessible = isTransactionAccessible( sf, transactionCoordinatorBuilder ); + + this.defaultSessionProperties = initializeDefaultSessionProperties( sf ); + this.defaultCacheStoreMode = determineCacheStoreMode( defaultSessionProperties ); + this.defaultCacheRetrieveMode = determineCacheRetrieveMode( defaultSessionProperties ); + this.initialSessionCacheMode = CacheModeHelper.interpretCacheMode( defaultCacheStoreMode, defaultCacheRetrieveMode ); + this.discardOnClose = sessionFactoryOptions.isReleaseResourcesOnCloseEnabled(); + this.defaultJdbcObservers = new ConnectionObserverStatsBridge( sf ); + this.defaultSessionEventListeners = sessionFactoryOptions.getBaselineSessionEventsListenerBuilder(); + this.defaultLockOptions = initializeDefaultLockOptions( defaultSessionProperties ); + } + + private static LockOptions initializeDefaultLockOptions(final Map defaultSessionProperties) { + LockOptions def = new LockOptions(); + LockOptionsHelper.applyPropertiesToLockOptions( defaultSessionProperties, () -> def ); + return def; + } + + private static EventListenerGroup listeners(EventListenerRegistry elr, EventType type) { + return elr.getEventListenerGroup( type ); + } + + SqlTypeDescriptor remapSqlTypeDescriptor(SqlTypeDescriptor sqlTypeDescriptor) { + if ( !sqlTypeDescriptor.canBeRemapped() ) { + return sqlTypeDescriptor; + } + + final SqlTypeDescriptor remapped = dialect.remapSqlTypeDescriptor( sqlTypeDescriptor ); + return remapped == null ? sqlTypeDescriptor : remapped; + } + + private static boolean isTransactionAccessible(SessionFactoryImpl sf, TransactionCoordinatorBuilder transactionCoordinatorBuilder) { + // JPA requires that access not be provided to the transaction when using JTA. + // This is overridden when SessionFactoryOptions isJtaTransactionAccessEnabled() is true. + if ( sf.getSessionFactoryOptions().getJpaCompliance().isJpaTransactionComplianceEnabled() && + transactionCoordinatorBuilder.isJta() && + !sf.getSessionFactoryOptions().isJtaTransactionAccessEnabled() ) { + return false; + } + return true; + } + + private static Map initializeDefaultSessionProperties(SessionFactoryImpl sf) { + HashMap p = new HashMap<>(); + + //Static defaults: + p.putIfAbsent( AvailableSettings.FLUSH_MODE, FlushMode.AUTO.name() ); + p.putIfAbsent( JPA_LOCK_SCOPE, PessimisticLockScope.EXTENDED.name() ); + p.putIfAbsent( JPA_LOCK_TIMEOUT, LockOptions.WAIT_FOREVER ); + p.putIfAbsent( JPA_SHARED_CACHE_RETRIEVE_MODE, CacheModeHelper.DEFAULT_RETRIEVE_MODE ); + p.putIfAbsent( JPA_SHARED_CACHE_STORE_MODE, CacheModeHelper.DEFAULT_STORE_MODE ); + + //Defaults defined by SessionFactory configuration: + final String[] ENTITY_MANAGER_SPECIFIC_PROPERTIES = { + JPA_LOCK_SCOPE, + JPA_LOCK_TIMEOUT, + AvailableSettings.FLUSH_MODE, + JPA_SHARED_CACHE_RETRIEVE_MODE, + JPA_SHARED_CACHE_STORE_MODE, + QueryHints.SPEC_HINT_TIMEOUT + }; + final Map properties = sf.getProperties(); + for ( String key : ENTITY_MANAGER_SPECIFIC_PROPERTIES ) { + if ( properties.containsKey( key ) ) { + p.put( key, properties.get( key ) ); + } + } + return Collections.unmodifiableMap( p ); + } + + /** + * @param properties the Session properties + * @return either the CacheStoreMode as defined in the Session specific properties, or as defined in the + * properties shared across all sessions (the defaults). + */ + CacheStoreMode getCacheStoreMode(final Map properties) { + if ( properties == null ) { + return this.defaultCacheStoreMode; + } + else { + return determineCacheStoreMode( properties ); + } + } + + /** + * @param properties the Session properties + * @return either the CacheRetrieveMode as defined in the Session specific properties, or as defined in the + * properties shared across all sessions (the defaults). + */ + CacheRetrieveMode getCacheRetrieveMode(Map properties) { + if ( properties == null ) { + return this.defaultCacheRetrieveMode; + } + else { + return determineCacheRetrieveMode( properties ); + } + } + + private static CacheRetrieveMode determineCacheRetrieveMode(Map settings) { + return ( CacheRetrieveMode ) settings.get( JPA_SHARED_CACHE_RETRIEVE_MODE ); + } + + private static CacheStoreMode determineCacheStoreMode(Map settings) { + return ( CacheStoreMode ) settings.get( JPA_SHARED_CACHE_STORE_MODE ); + } + + public ConnectionObserverStatsBridge getDefaultJdbcObserver() { + return defaultJdbcObservers; + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/internal/IteratorImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/IteratorImpl.java index e6db72e7ba..5e62e4a30c 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/IteratorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/IteratorImpl.java @@ -105,7 +105,7 @@ public final class IteratorImpl implements HibernateIterator { try { boolean isHolder = holderInstantiator.isRequired(); - LOG.debugf( "Assembling results" ); + LOG.debug( "Assembling results" ); if ( single && !isHolder ) { currentResult = types[0].nullSafeGet( rs, names[0], session, null ); } @@ -124,7 +124,7 @@ public final class IteratorImpl implements HibernateIterator { } postNext(); - LOG.debugf( "Returning current results" ); + LOG.debug( "Returning current results" ); return currentResult; } catch (SQLException sqle) { diff --git a/hibernate-core/src/main/java/org/hibernate/internal/JdbcObserverImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/JdbcObserverImpl.java index ef02f759a4..015aca4385 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/JdbcObserverImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/JdbcObserverImpl.java @@ -7,81 +7,73 @@ package org.hibernate.internal; import java.sql.Connection; -import java.util.ArrayList; -import java.util.List; -import org.hibernate.engine.jdbc.spi.ConnectionObserver; +import org.hibernate.engine.spi.SessionEventListenerManager; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.resource.jdbc.spi.JdbcObserver; /** * @author Steve Ebersole */ -public class JdbcObserverImpl implements JdbcObserver { - private final SharedSessionContractImplementor session; - private final transient List observers; +public final class JdbcObserverImpl implements JdbcObserver { - public JdbcObserverImpl(SharedSessionContractImplementor session) { + private final ConnectionObserverStatsBridge observer; + private final SessionEventListenerManager eventListenerManager; + private final SharedSessionContractImplementor session; + + public JdbcObserverImpl(SharedSessionContractImplementor session, FastSessionServices fastSessionServices) { this.session = session; - this.observers = new ArrayList<>(); - this.observers.add( new ConnectionObserverStatsBridge( session.getFactory() ) ); + this.observer = fastSessionServices.getDefaultJdbcObserver(); + this.eventListenerManager = session.getEventListenerManager(); } @Override public void jdbcConnectionAcquisitionStart() { - } @Override public void jdbcConnectionAcquisitionEnd(Connection connection) { - for ( ConnectionObserver observer : observers ) { - observer.physicalConnectionObtained( connection ); - } + observer.physicalConnectionObtained( connection ); } @Override public void jdbcConnectionReleaseStart() { - } @Override public void jdbcConnectionReleaseEnd() { - for ( ConnectionObserver observer : observers ) { - observer.physicalConnectionReleased(); - } + observer.physicalConnectionReleased(); } @Override public void jdbcPrepareStatementStart() { - session.getEventListenerManager().jdbcPrepareStatementStart(); + eventListenerManager.jdbcPrepareStatementStart(); } @Override public void jdbcPrepareStatementEnd() { - for ( ConnectionObserver observer : observers ) { - observer.statementPrepared(); - } - session.getEventListenerManager().jdbcPrepareStatementEnd(); + observer.statementPrepared(); + eventListenerManager.jdbcPrepareStatementEnd(); } @Override public void jdbcExecuteStatementStart() { - session.getEventListenerManager().jdbcExecuteStatementStart(); + eventListenerManager.jdbcExecuteStatementStart(); } @Override public void jdbcExecuteStatementEnd() { - session.getEventListenerManager().jdbcExecuteStatementEnd(); + eventListenerManager.jdbcExecuteStatementEnd(); } @Override public void jdbcExecuteBatchStart() { - session.getEventListenerManager().jdbcExecuteBatchStart(); + eventListenerManager.jdbcExecuteBatchStart(); } @Override public void jdbcExecuteBatchEnd() { - session.getEventListenerManager().jdbcExecuteBatchEnd(); + eventListenerManager.jdbcExecuteBatchEnd(); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/internal/JdbcSessionContextImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/JdbcSessionContextImpl.java index da6c9f3ca7..954593d6d5 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/JdbcSessionContextImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/JdbcSessionContextImpl.java @@ -28,12 +28,15 @@ public class JdbcSessionContextImpl implements JdbcSessionContext { private final transient ServiceRegistry serviceRegistry; private final transient JdbcObserver jdbcObserver; - public JdbcSessionContextImpl(SharedSessionContractImplementor session, StatementInspector statementInspector) { + public JdbcSessionContextImpl( + SharedSessionContractImplementor session, + StatementInspector statementInspector, + FastSessionServices fastSessionServices) { this.sessionFactory = session.getFactory(); this.statementInspector = statementInspector; this.connectionHandlingMode = settings().getPhysicalConnectionHandlingMode(); this.serviceRegistry = sessionFactory.getServiceRegistry(); - this.jdbcObserver = new JdbcObserverImpl( session ); + this.jdbcObserver = new JdbcObserverImpl( session, fastSessionServices ); if ( this.statementInspector == null ) { throw new IllegalArgumentException( "StatementInspector cannot be null" ); diff --git a/hibernate-core/src/main/java/org/hibernate/internal/NonContextualJdbcConnectionAccess.java b/hibernate-core/src/main/java/org/hibernate/internal/NonContextualJdbcConnectionAccess.java index 4cbe9573c9..82df15f40f 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/NonContextualJdbcConnectionAccess.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/NonContextualJdbcConnectionAccess.java @@ -9,6 +9,7 @@ package org.hibernate.internal; import java.io.Serializable; import java.sql.Connection; import java.sql.SQLException; +import java.util.Objects; import org.hibernate.SessionEventListener; import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; @@ -24,6 +25,8 @@ public class NonContextualJdbcConnectionAccess implements JdbcConnectionAccess, public NonContextualJdbcConnectionAccess( SessionEventListener listener, ConnectionProvider connectionProvider) { + Objects.requireNonNull( listener ); + Objects.requireNonNull( connectionProvider ); this.listener = listener; this.connectionProvider = connectionProvider; } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/SessionCreationOptions.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionCreationOptions.java index a647708e18..ed2920662e 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionCreationOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionCreationOptions.java @@ -7,10 +7,12 @@ package org.hibernate.internal; import java.sql.Connection; +import java.util.List; import java.util.TimeZone; import org.hibernate.FlushMode; import org.hibernate.Interceptor; +import org.hibernate.SessionEventListener; import org.hibernate.engine.spi.SessionOwner; import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode; import org.hibernate.resource.jdbc.spi.StatementInspector; @@ -45,6 +47,12 @@ public interface SessionCreationOptions { TimeZone getJdbcTimeZone(); + /** + * @return the full list of SessionEventListener if this was customized, + * or null if this Session is being created with the default list. + */ + List getCustomSessionEventListener(); + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // deprecations diff --git a/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java index 1f380c8cd4..c0176cffe1 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java @@ -68,6 +68,7 @@ import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider; +import org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.jndi.spi.JndiService; import org.hibernate.engine.profile.Association; @@ -196,6 +197,10 @@ public final class SessionFactoryImpl implements SessionFactoryImplementor { private final transient TypeHelper typeHelper; + private final transient FastSessionServices fastSessionServices; + private final transient SessionBuilder defaultSessionOpenOptions; + private final transient SessionBuilder temporarySessionOpenOptions; + public SessionFactoryImpl( final MetadataImplementor metadata, SessionFactoryOptions options) { @@ -374,6 +379,13 @@ public final class SessionFactoryImpl implements SessionFactoryImplementor { fetchProfiles.put( fetchProfile.getName(), fetchProfile ); } + this.defaultSessionOpenOptions = withOptions(); + this.temporarySessionOpenOptions = withOptions() + .autoClose( false ) + .flushMode( FlushMode.MANUAL ) + .connectionHandlingMode( PhysicalConnectionHandlingMode.DELAYED_ACQUISITION_AND_RELEASE_AFTER_STATEMENT ); + this.fastSessionServices = new FastSessionServices( this ); + this.observer.sessionFactoryCreated( this ); SessionFactoryRegistry.INSTANCE.addSessionFactory( @@ -458,41 +470,22 @@ public final class SessionFactoryImpl implements SessionFactoryImplementor { } private JdbcConnectionAccess buildLocalConnectionAccess() { - return new JdbcConnectionAccess() { - @Override - public Connection obtainConnection() throws SQLException { - return !settings.getMultiTenancyStrategy().requiresMultiTenantConnectionProvider() - ? serviceRegistry.getService( ConnectionProvider.class ).getConnection() - : serviceRegistry.getService( MultiTenantConnectionProvider.class ).getAnyConnection(); - } - - @Override - public void releaseConnection(Connection connection) throws SQLException { - if ( !settings.getMultiTenancyStrategy().requiresMultiTenantConnectionProvider() ) { - serviceRegistry.getService( ConnectionProvider.class ).closeConnection( connection ); - } - else { - serviceRegistry.getService( MultiTenantConnectionProvider.class ).releaseAnyConnection( connection ); - } - } - - @Override - public boolean supportsAggressiveRelease() { - return false; - } - }; + if ( settings.getMultiTenancyStrategy().requiresMultiTenantConnectionProvider() ) { + final MultiTenantConnectionProvider mTenantConnectionProvider = serviceRegistry.getService( MultiTenantConnectionProvider.class ); + return new JdbcEnvironmentInitiator.MultiTenantConnectionProviderJdbcConnectionAccess( mTenantConnectionProvider ); + } + else { + final ConnectionProvider connectionProvider = serviceRegistry.getService( ConnectionProvider.class ); + return new JdbcEnvironmentInitiator.ConnectionProviderJdbcConnectionAccess( connectionProvider ); + } } public Session openSession() throws HibernateException { - return withOptions().openSession(); + return this.defaultSessionOpenOptions.openSession(); } public Session openTemporarySession() throws HibernateException { - return withOptions() - .autoClose( false ) - .flushMode( FlushMode.MANUAL ) - .connectionHandlingMode( PhysicalConnectionHandlingMode.DELAYED_ACQUISITION_AND_RELEASE_AFTER_STATEMENT ) - .openSession(); + return this.temporarySessionOpenOptions.openSession(); } public Session getCurrentSession() throws HibernateException { @@ -1166,11 +1159,13 @@ public final class SessionFactoryImpl implements SessionFactoryImplementor { private TimeZone jdbcTimeZone; private boolean queryParametersValidationEnabled; + // Lazy: defaults can be built by invoking the builder in fastSessionServices.defaultSessionEventListeners + // (Need a fresh build for each Session as the listener instances can't be reused across sessions) + // Only initialize of the builder is overriding the default. private List listeners; //todo : expose setting private SessionOwnerBehavior sessionOwnerBehavior = SessionOwnerBehavior.LEGACY_NATIVE; - private PersistenceUnitTransactionType persistenceUnitTransactionType; SessionBuilderImpl(SessionFactoryImpl sessionFactory) { this.sessionFactory = sessionFactory; @@ -1189,9 +1184,7 @@ public final class SessionFactoryImpl implements SessionFactoryImplementor { tenantIdentifier = currentTenantIdentifierResolver.resolveCurrentTenantIdentifier(); } this.jdbcTimeZone = sessionFactoryOptions.getJdbcTimeZone(); - - listeners = sessionFactoryOptions.getBaselineSessionEventsListenerBuilder().buildBaselineList(); - queryParametersValidationEnabled = sessionFactoryOptions.isQueryParametersValidationEnabled(); + this.queryParametersValidationEnabled = sessionFactoryOptions.isQueryParametersValidationEnabled(); } @@ -1279,20 +1272,18 @@ public final class SessionFactoryImpl implements SessionFactoryImplementor { return jdbcTimeZone; } + @Override + public List getCustomSessionEventListener() { + return listeners; + } + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // SessionBuilder @Override public Session openSession() { log.tracef( "Opening Hibernate Session. tenant=%s", tenantIdentifier ); - final SessionImpl session = new SessionImpl( sessionFactory, this ); - - final SessionEventListenerManager eventListenerManager = session.getEventListenerManager(); - for ( SessionEventListener listener : listeners ) { - eventListenerManager.addListener( listener ); - } - - return session; + return new SessionImpl( sessionFactory, this ); } @Override @@ -1388,6 +1379,11 @@ public final class SessionFactoryImpl implements SessionFactoryImplementor { @Override @SuppressWarnings("unchecked") public T eventListeners(SessionEventListener... listeners) { + if ( this.listeners == null ) { + this.listeners = sessionFactory.getSessionFactoryOptions() + .getBaselineSessionEventsListenerBuilder() + .buildBaselineList(); + } Collections.addAll( this.listeners, listeners ); return (T) this; } @@ -1395,7 +1391,13 @@ public final class SessionFactoryImpl implements SessionFactoryImplementor { @Override @SuppressWarnings("unchecked") public T clearEventListeners() { - listeners.clear(); + if ( listeners == null ) { + //Needs to initialize explicitly to an empty list as otherwise "null" immplies the default listeners will be applied + this.listeners = new ArrayList( 3 ); + } + else { + listeners.clear(); + } return (T) this; } @@ -1495,6 +1497,11 @@ public final class SessionFactoryImpl implements SessionFactoryImplementor { return sessionFactory.getSessionFactoryOptions().getJdbcTimeZone(); } + @Override + public List getCustomSessionEventListener() { + return null; + } + @Override public SessionOwner getSessionOwner() { return null; @@ -1659,4 +1666,12 @@ public final class SessionFactoryImpl implements SessionFactoryImplementor { LOG.emptyCompositesEnabled(); } } + + /** + * @return the FastSessionServices for this SessionFactory. + */ + FastSessionServices getFastSessionServices() { + return this.fastSessionServices; + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryRegistry.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryRegistry.java index e3d836ddd7..ec94c1ceda 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryRegistry.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryRegistry.java @@ -155,7 +155,7 @@ public class SessionFactoryRegistry { final SessionFactory sessionFactory = sessionFactoryMap.get( uuid ); if ( sessionFactory == null && LOG.isDebugEnabled() ) { LOG.debugf( "Not found: %s", uuid ); - LOG.debugf( sessionFactoryMap.toString() ); + LOG.debug( sessionFactoryMap.toString() ); } return sessionFactory; } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java index 4883160153..656feaa482 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -96,8 +96,6 @@ import org.hibernate.engine.spi.Status; import org.hibernate.engine.spi.TypedValue; import org.hibernate.engine.transaction.spi.TransactionImplementor; import org.hibernate.engine.transaction.spi.TransactionObserver; -import org.hibernate.event.service.spi.EventListenerGroup; -import org.hibernate.event.service.spi.EventListenerRegistry; import org.hibernate.event.spi.AutoFlushEvent; import org.hibernate.event.spi.AutoFlushEventListener; import org.hibernate.event.spi.ClearEvent; @@ -107,7 +105,6 @@ import org.hibernate.event.spi.DeleteEventListener; import org.hibernate.event.spi.DirtyCheckEvent; import org.hibernate.event.spi.DirtyCheckEventListener; import org.hibernate.event.spi.EventSource; -import org.hibernate.event.spi.EventType; import org.hibernate.event.spi.EvictEvent; import org.hibernate.event.spi.EvictEventListener; import org.hibernate.event.spi.FlushEvent; @@ -148,6 +145,7 @@ import org.hibernate.jpa.internal.util.CacheModeHelper; import org.hibernate.jpa.internal.util.ConfigurationHelper; import org.hibernate.jpa.internal.util.FlushModeTypeHelper; import org.hibernate.jpa.internal.util.LockModeTypeHelper; +import org.hibernate.jpa.internal.util.LockOptionsHelper; import org.hibernate.jpa.spi.HibernateEntityManagerImplementor; import org.hibernate.loader.criteria.CriteriaLoader; import org.hibernate.loader.custom.CustomLoader; @@ -170,8 +168,6 @@ import org.hibernate.query.internal.CollectionFilterImpl; import org.hibernate.query.spi.ScrollableResultsImplementor; import org.hibernate.resource.transaction.TransactionRequiredForJoinException; import org.hibernate.resource.transaction.backend.jta.internal.JtaTransactionCoordinatorImpl; -import org.hibernate.resource.transaction.backend.jta.internal.synchronization.AfterCompletionAction; -import org.hibernate.resource.transaction.backend.jta.internal.synchronization.ManagedFlushChecker; import org.hibernate.resource.transaction.spi.TransactionCoordinator; import org.hibernate.resource.transaction.spi.TransactionStatus; import org.hibernate.stat.SessionStatistics; @@ -197,31 +193,22 @@ import static org.hibernate.cfg.AvailableSettings.JPA_SHARED_CACHE_STORE_MODE; * @author Steve Ebersole * @author Brett Meyer * @author Chris Cranford + * @author Sanne Grinovero */ public final class SessionImpl extends AbstractSessionImpl implements EventSource, SessionImplementor, HibernateEntityManagerImplementor { private static final EntityManagerMessageLogger log = HEMLogging.messageLogger( SessionImpl.class ); - - private static final String[] ENTITY_MANAGER_SPECIFIC_PROPERTIES = { - JPA_LOCK_SCOPE, - JPA_LOCK_TIMEOUT, - AvailableSettings.FLUSH_MODE, - JPA_SHARED_CACHE_RETRIEVE_MODE, - JPA_SHARED_CACHE_STORE_MODE, - QueryHints.SPEC_HINT_TIMEOUT - }; - - private Map properties = new HashMap<>(); + // Defaults to null which means the properties are the default - as defined in FastSessionServices#defaultSessionProperties + private Map properties; private transient ActionQueue actionQueue; private transient StatefulPersistenceContext persistenceContext; private transient LoadQueryInfluencers loadQueryInfluencers; - // todo : (5.2) HEM always initialized this. Is that really needed? - private LockOptions lockOptions = new LockOptions(); + private LockOptions lockOptions; private boolean autoClear; private boolean autoClose; @@ -231,10 +218,7 @@ public final class SessionImpl private transient LoadEvent loadEvent; //cached LoadEvent instance - private transient boolean discardOnClose; - private transient TransactionObserver transactionObserver; - private transient EventListenerRegistry eventListenerRegistry; public SessionImpl(SessionFactoryImpl factory, SessionCreationOptions options) { super( factory, options ); @@ -246,8 +230,6 @@ public final class SessionImpl this.autoClose = options.shouldAutoClose(); this.queryParametersValidationEnabled = options.isQueryParametersValidationEnabled(); - this.discardOnClose = factory.getSessionFactoryOptions().isReleaseResourcesOnCloseEnabled(); - if ( options instanceof SharedSessionCreationOptions ) { final SharedSessionCreationOptions sharedOptions = (SharedSessionCreationOptions) options; final ActionQueue.TransactionCompletionProcesses transactionCompletionProcesses = sharedOptions.getTransactionCompletionProcesses(); @@ -266,67 +248,53 @@ public final class SessionImpl statistics.openSession(); } + if ( this.properties != null ) { + //There might be custom properties for this session that affect the LockOptions state + LockOptionsHelper.applyPropertiesToLockOptions( this.properties, this::getLockOptionsForWrite ); + } + getSession().setCacheMode( fastSessionServices.initialSessionCacheMode ); + // NOTE : pulse() already handles auto-join-ability correctly getTransactionCoordinator().pulse(); - setDefaultProperties(); - applyProperties(); - if ( log.isTraceEnabled() ) { log.tracef( "Opened Session [%s] at timestamp: %s", getSessionIdentifier(), getTimestamp() ); } } - private void setDefaultProperties() { - properties.putIfAbsent( AvailableSettings.FLUSH_MODE, getHibernateFlushMode().name() ); - properties.putIfAbsent( JPA_LOCK_SCOPE, PessimisticLockScope.EXTENDED.name() ); - properties.putIfAbsent( JPA_LOCK_TIMEOUT, LockOptions.WAIT_FOREVER ); - properties.putIfAbsent( JPA_SHARED_CACHE_RETRIEVE_MODE, CacheModeHelper.DEFAULT_RETRIEVE_MODE ); - properties.putIfAbsent( JPA_SHARED_CACHE_STORE_MODE, CacheModeHelper.DEFAULT_STORE_MODE ); + private LockOptions getLockOptionsForRead() { + return this.lockOptions == null ? fastSessionServices.defaultLockOptions : this.lockOptions; } - - private void applyProperties() { - applyEntityManagerSpecificProperties(); - setHibernateFlushMode( ConfigurationHelper.getFlushMode( properties.get( AvailableSettings.FLUSH_MODE ), FlushMode.AUTO ) ); - setLockOptions( this.properties, this.lockOptions ); - getSession().setCacheMode( - CacheModeHelper.interpretCacheMode( - currentCacheStoreMode(), - currentCacheRetrieveMode() - ) - ); - } - - private void applyEntityManagerSpecificProperties() { - final Map properties = getFactory().getProperties(); - for ( String key : ENTITY_MANAGER_SPECIFIC_PROPERTIES ) { - if ( properties.containsKey( key ) ) { - this.properties.put( key, properties.get( key ) ); - } + private LockOptions getLockOptionsForWrite() { + if ( this.lockOptions == null ) { + this.lockOptions = new LockOptions(); } + return this.lockOptions; } protected void applyQuerySettingsAndHints(Query query) { - if ( lockOptions.getLockMode() != LockMode.NONE ) { - query.setLockMode( getLockMode( lockOptions.getLockMode() ) ); + final LockOptions lockOptionsForRead = getLockOptionsForRead(); + if ( lockOptionsForRead.getLockMode() != LockMode.NONE ) { + query.setLockMode( getLockMode( lockOptionsForRead.getLockMode() ) ); } final Object queryTimeout; - if ( ( queryTimeout = properties.get( QueryHints.SPEC_HINT_TIMEOUT ) ) != null ) { + if ( ( queryTimeout = getSessionProperty( QueryHints.SPEC_HINT_TIMEOUT ) ) != null ) { query.setHint( QueryHints.SPEC_HINT_TIMEOUT, queryTimeout ); } final Object lockTimeout; - if ( ( lockTimeout = properties.get( JPA_LOCK_TIMEOUT ) ) != null ) { + if ( ( lockTimeout = getSessionProperty( JPA_LOCK_TIMEOUT ) ) != null ) { query.setHint( JPA_LOCK_TIMEOUT, lockTimeout ); } } - private CacheRetrieveMode currentCacheRetrieveMode() { - return determineCacheRetrieveMode( properties ); - } - - private CacheStoreMode currentCacheStoreMode() { - return determineCacheStoreMode( properties ); + private Object getSessionProperty(final String name) { + if ( properties == null ) { + return fastSessionServices.defaultSessionProperties.get( name ); + } + else { + return properties.get( name ); + } } @Override @@ -354,13 +322,12 @@ public final class SessionImpl persistenceContext.clear(); actionQueue.clear(); - final ClearEvent event = new ClearEvent( this ); - for ( ClearEventListener listener : listeners( EventType.CLEAR ) ) { - listener.onClear( event ); - } + fastSessionServices.eventListenerGroup_CLEAR.fireLazyEventOnEachListener( this::createClearEvent, ClearEventListener::onClear ); } - + private ClearEvent createClearEvent() { + return new ClearEvent( this ); + } @Override @SuppressWarnings("StatementWithEmptyBody") @@ -388,7 +355,7 @@ public final class SessionImpl // Original hibernate-entitymanager EM#close behavior checkSessionFactoryOpen(); checkOpenOrWaitingForAutoClose(); - if ( discardOnClose || !isTransactionInProgress( false ) ) { + if ( fastSessionServices.discardOnClose || !isTransactionInProgress( false ) ) { super.close(); } else { @@ -422,7 +389,8 @@ public final class SessionImpl return super.shouldCloseJdbcCoordinatorOnClose( isTransactionCoordinatorShared ); } - if ( getActionQueue().hasBeforeTransactionActions() || getActionQueue().hasAfterTransactionActions() ) { + final ActionQueue actionQueue = getActionQueue(); + if ( actionQueue.hasBeforeTransactionActions() || actionQueue.hasAfterTransactionActions() ) { log.warn( "On close, shared Session had before/after transaction actions that have not yet been processed" ); @@ -459,10 +427,6 @@ public final class SessionImpl } } - private boolean isFlushModeNever() { - return FlushMode.isManualFlushMode( getHibernateFlushMode() ); - } - private void managedFlush() { if ( isClosed() && !waitingForAutoClose ) { log.trace( "Skipping auto-flush due to session closed" ); @@ -628,28 +592,14 @@ public final class SessionImpl fireSaveOrUpdate( new SaveOrUpdateEvent( entityName, obj, this ) ); } - private void fireSaveOrUpdate(SaveOrUpdateEvent event) { + private void fireSaveOrUpdate(final SaveOrUpdateEvent event) { checkOpen(); checkTransactionSynchStatus(); checkNoUnresolvedActionsBeforeOperation(); - for ( SaveOrUpdateEventListener listener : listeners( EventType.SAVE_UPDATE ) ) { - listener.onSaveOrUpdate( event ); - } + fastSessionServices.eventListenerGroup_SAVE_UPDATE.fireEventOnEachListener( event, SaveOrUpdateEventListener::onSaveOrUpdate ); checkNoUnresolvedActionsAfterOperation(); } - private Iterable listeners(EventType type) { - return eventListenerGroup( type ).listeners(); - } - - private EventListenerGroup eventListenerGroup(EventType type) { - if ( this.eventListenerRegistry == null ) { - this.eventListenerRegistry = getFactory().getServiceRegistry().getService( EventListenerRegistry.class ); - } - return eventListenerRegistry.getEventListenerGroup( type ); - } - - // save() operations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @Override @@ -662,13 +612,11 @@ public final class SessionImpl return fireSave( new SaveOrUpdateEvent( entityName, object, this ) ); } - private Serializable fireSave(SaveOrUpdateEvent event) { + private Serializable fireSave(final SaveOrUpdateEvent event) { checkOpen(); checkTransactionSynchStatus(); checkNoUnresolvedActionsBeforeOperation(); - for ( SaveOrUpdateEventListener listener : listeners( EventType.SAVE ) ) { - listener.onSaveOrUpdate( event ); - } + fastSessionServices.eventListenerGroup_SAVE.fireEventOnEachListener( event, SaveOrUpdateEventListener::onSaveOrUpdate ); checkNoUnresolvedActionsAfterOperation(); return event.getResultId(); } @@ -690,9 +638,7 @@ public final class SessionImpl checkOpen(); checkTransactionSynchStatus(); checkNoUnresolvedActionsBeforeOperation(); - for ( SaveOrUpdateEventListener listener : listeners( EventType.UPDATE ) ) { - listener.onSaveOrUpdate( event ); - } + fastSessionServices.eventListenerGroup_UPDATE.fireEventOnEachListener( event, SaveOrUpdateEventListener::onSaveOrUpdate ); checkNoUnresolvedActionsAfterOperation(); } @@ -725,13 +671,10 @@ public final class SessionImpl private void fireLock(LockEvent event) { checkOpen(); pulseTransactionCoordinator(); - for ( LockEventListener listener : listeners( EventType.LOCK ) ) { - listener.onLock( event ); - } + fastSessionServices.eventListenerGroup_LOCK.fireEventOnEachListener( event, LockEventListener::onLock ); delayedAfterCompletion(); } - // persist() operations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @Override @@ -752,14 +695,12 @@ public final class SessionImpl firePersist( copiedAlready, new PersistEvent( entityName, object, this ) ); } - private void firePersist(PersistEvent event) { + private void firePersist(final PersistEvent event) { try { checkTransactionSynchStatus(); checkNoUnresolvedActionsBeforeOperation(); - for ( PersistEventListener listener : listeners( EventType.PERSIST ) ) { - listener.onPersist( event ); - } + fastSessionServices.eventListenerGroup_PERSIST.fireEventOnEachListener( event, PersistEventListener::onPersist ); } catch (MappingException e) { throw getExceptionConverter().convert( new IllegalArgumentException( e.getMessage() ) ); @@ -777,13 +718,13 @@ public final class SessionImpl } } - private void firePersist(Map copiedAlready, PersistEvent event) { + private void firePersist(final Map copiedAlready, final PersistEvent event) { pulseTransactionCoordinator(); try { - for ( PersistEventListener listener : listeners( EventType.PERSIST ) ) { - listener.onPersist( event, copiedAlready ); - } + //Uses a capturing lambda in this case as we need to carry the additional Map parameter: + fastSessionServices.eventListenerGroup_PERSIST + .fireEventOnEachListener( event, copiedAlready, PersistEventListener::onPersist ); } catch ( MappingException e ) { throw getExceptionConverter().convert( new IllegalArgumentException( e.getMessage() ) ) ; @@ -799,41 +740,15 @@ public final class SessionImpl // persistOnFlush() operations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - public void persistOnFlush(String entityName, Object object) - throws HibernateException { - firePersistOnFlush( new PersistEvent( entityName, object, this ) ); - } - - public void persistOnFlush(Object object) throws HibernateException { - persist( null, object ); - } - @Override - public void persistOnFlush(String entityName, Object object, Map copiedAlready) - throws HibernateException { - firePersistOnFlush( copiedAlready, new PersistEvent( entityName, object, this ) ); - } - - private void firePersistOnFlush(Map copiedAlready, PersistEvent event) { + public void persistOnFlush(String entityName, Object object, Map copiedAlready) { checkOpenOrWaitingForAutoClose(); pulseTransactionCoordinator(); - for ( PersistEventListener listener : listeners( EventType.PERSIST_ONFLUSH ) ) { - listener.onPersist( event, copiedAlready ); - } + PersistEvent event = new PersistEvent( entityName, object, this ); + fastSessionServices.eventListenerGroup_PERSIST_ONFLUSH.fireEventOnEachListener( event, copiedAlready, PersistEventListener::onPersist ); delayedAfterCompletion(); } - private void firePersistOnFlush(PersistEvent event) { - checkOpen(); - checkTransactionSynchStatus(); - checkNoUnresolvedActionsBeforeOperation(); - for ( PersistEventListener listener : listeners( EventType.PERSIST_ONFLUSH ) ) { - listener.onPersist( event ); - } - checkNoUnresolvedActionsAfterOperation(); - } - - // merge() operations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @Override @@ -858,9 +773,7 @@ public final class SessionImpl try { checkTransactionSynchStatus(); checkNoUnresolvedActionsBeforeOperation(); - for ( MergeEventListener listener : listeners( EventType.MERGE ) ) { - listener.onMerge( event ); - } + fastSessionServices.eventListenerGroup_MERGE.fireEventOnEachListener( event, MergeEventListener::onMerge ); checkNoUnresolvedActionsAfterOperation(); } catch ( ObjectDeletedException sse ) { @@ -877,12 +790,10 @@ public final class SessionImpl return event.getResult(); } - private void fireMerge(Map copiedAlready, MergeEvent event) { + private void fireMerge(final Map copiedAlready, final MergeEvent event) { try { pulseTransactionCoordinator(); - for ( MergeEventListener listener : listeners( EventType.MERGE ) ) { - listener.onMerge( event, copiedAlready ); - } + fastSessionServices.eventListenerGroup_MERGE.fireEventOnEachListener( event, copiedAlready, MergeEventListener::onMerge ); } catch ( ObjectDeletedException sse ) { throw getExceptionConverter().convert( new IllegalArgumentException( sse ) ); @@ -918,7 +829,9 @@ public final class SessionImpl public void delete(String entityName, Object object, boolean isCascadeDeleteEnabled, Set transientEntities) throws HibernateException { checkOpenOrWaitingForAutoClose(); - if ( log.isTraceEnabled() && persistenceContext.isRemovingOrphanBeforeUpates() ) { + final boolean removingOrphanBeforeUpates = persistenceContext.isRemovingOrphanBeforeUpates(); + final boolean traceEnabled = log.isTraceEnabled(); + if ( traceEnabled && removingOrphanBeforeUpates ) { logRemoveOrphanBeforeUpdates( "before continuing", entityName, object ); } fireDelete( @@ -926,12 +839,12 @@ public final class SessionImpl entityName, object, isCascadeDeleteEnabled, - persistenceContext.isRemovingOrphanBeforeUpates(), + removingOrphanBeforeUpates, this ), transientEntities ); - if ( log.isTraceEnabled() && persistenceContext.isRemovingOrphanBeforeUpates() ) { + if ( traceEnabled && removingOrphanBeforeUpates ) { logRemoveOrphanBeforeUpdates( "after continuing", entityName, object ); } } @@ -940,7 +853,8 @@ public final class SessionImpl public void removeOrphanBeforeUpdates(String entityName, Object child) { // TODO: The removeOrphan concept is a temporary "hack" for HHH-6484. This should be removed once action/task // ordering is improved. - if ( log.isTraceEnabled() ) { + final boolean traceEnabled = log.isTraceEnabled(); + if ( traceEnabled ) { logRemoveOrphanBeforeUpdates( "begin", entityName, child ); } persistenceContext.beginRemoveOrphanBeforeUpdates(); @@ -950,27 +864,27 @@ public final class SessionImpl } finally { persistenceContext.endRemoveOrphanBeforeUpdates(); - if ( log.isTraceEnabled() ) { + if ( traceEnabled ) { logRemoveOrphanBeforeUpdates( "end", entityName, child ); } } } private void logRemoveOrphanBeforeUpdates(String timing, String entityName, Object entity) { - final EntityEntry entityEntry = persistenceContext.getEntry( entity ); - log.tracef( - "%s remove orphan before updates: [%s]", - timing, - entityEntry == null ? entityName : MessageHelper.infoString( entityName, entityEntry.getId() ) - ); + if ( log.isTraceEnabled() ) { + final EntityEntry entityEntry = persistenceContext.getEntry( entity ); + log.tracef( + "%s remove orphan before updates: [%s]", + timing, + entityEntry == null ? entityName : MessageHelper.infoString( entityName, entityEntry.getId() ) + ); + } } - private void fireDelete(DeleteEvent event) { + private void fireDelete(final DeleteEvent event) { try{ pulseTransactionCoordinator(); - for ( DeleteEventListener listener : listeners( EventType.DELETE ) ) { - listener.onDelete( event ); - } + fastSessionServices.eventListenerGroup_DELETE.fireEventOnEachListener( event, DeleteEventListener::onDelete ); } catch ( ObjectDeletedException sse ) { throw getExceptionConverter().convert( new IllegalArgumentException( sse ) ); @@ -987,12 +901,10 @@ public final class SessionImpl } } - private void fireDelete(DeleteEvent event, Set transientEntities) { + private void fireDelete(final DeleteEvent event, final Set transientEntities) { try{ pulseTransactionCoordinator(); - for ( DeleteEventListener listener : listeners( EventType.DELETE ) ) { - listener.onDelete( event, transientEntities ); - } + fastSessionServices.eventListenerGroup_DELETE.fireEventOnEachListener( event, transientEntities, DeleteEventListener::onDelete ); } catch ( ObjectDeletedException sse ) { throw getExceptionConverter().convert( new IllegalArgumentException( sse ) ); @@ -1108,7 +1020,7 @@ public final class SessionImpl boolean clearedEffectiveGraph = false; if ( semantic != null ) { if ( ! graph.appliesTo( entityName ) ) { - log.debugf( "Clearing effective entity graph for subsequent-select" ); + log.debug( "Clearing effective entity graph for subsequent-select" ); clearedEffectiveGraph = true; effectiveEntityGraph.clear(); } @@ -1265,19 +1177,15 @@ public final class SessionImpl // so to skip the session open, transaction synch, etc.. checks, // which have been proven to be not particularly cheap: // it seems they prevent these hot methods from being inlined. - private void fireLoadNoChecks(LoadEvent event, LoadType loadType) { + private void fireLoadNoChecks(final LoadEvent event, final LoadType loadType) { pulseTransactionCoordinator(); - for ( LoadEventListener listener : listeners( EventType.LOAD ) ) { - listener.onLoad( event, loadType ); - } + fastSessionServices.eventListenerGroup_LOAD.fireEventOnEachListener( event, loadType, LoadEventListener::onLoad ); } - private void fireResolveNaturalId(ResolveNaturalIdEvent event) { + private void fireResolveNaturalId(final ResolveNaturalIdEvent event) { checkOpenOrWaitingForAutoClose(); pulseTransactionCoordinator(); - for ( ResolveNaturalIdEventListener listener : listeners( EventType.RESOLVE_NATURAL_ID ) ) { - listener.onResolveNaturalId( event ); - } + fastSessionServices.eventListenerGroup_RESOLVE_NATURAL_ID.fireEventOnEachListener( event, ResolveNaturalIdEventListener::onResolveNaturalId ); delayedAfterCompletion(); } @@ -1320,7 +1228,7 @@ public final class SessionImpl fireRefresh( refreshedAlready, new RefreshEvent( entityName, object, this ) ); } - private void fireRefresh(RefreshEvent event) { + private void fireRefresh(final RefreshEvent event) { try { if ( !getSessionFactory().getSessionFactoryOptions().isAllowRefreshDetachedEntity() ) { if ( event.getEntityName() != null ) { @@ -1335,9 +1243,7 @@ public final class SessionImpl } } pulseTransactionCoordinator(); - for ( RefreshEventListener listener : listeners( EventType.REFRESH ) ) { - listener.onRefresh( event ); - } + fastSessionServices.eventListenerGroup_REFRESH.fireEventOnEachListener( event, RefreshEventListener::onRefresh ); } catch (RuntimeException e) { if ( !getSessionFactory().getSessionFactoryOptions().isJpaBootstrap() ) { @@ -1353,12 +1259,10 @@ public final class SessionImpl } } - private void fireRefresh(Map refreshedAlready, RefreshEvent event) { + private void fireRefresh(final Map refreshedAlready, final RefreshEvent event) { try { pulseTransactionCoordinator(); - for ( RefreshEventListener listener : listeners( EventType.REFRESH ) ) { - listener.onRefresh( event, refreshedAlready ); - } + fastSessionServices.eventListenerGroup_REFRESH.fireEventOnEachListener( event, refreshedAlready, RefreshEventListener::onRefresh ); } catch (RuntimeException e) { throw getExceptionConverter().convert( e ); @@ -1382,12 +1286,10 @@ public final class SessionImpl fireReplicate( new ReplicateEvent( entityName, obj, replicationMode, this ) ); } - private void fireReplicate(ReplicateEvent event) { + private void fireReplicate(final ReplicateEvent event) { checkOpen(); pulseTransactionCoordinator(); - for ( ReplicateEventListener listener : listeners( EventType.REPLICATE ) ) { - listener.onReplicate( event ); - } + fastSessionServices.eventListenerGroup_REPLICATE.fireEventOnEachListener( event, ReplicateEventListener::onReplicate ); delayedAfterCompletion(); } @@ -1400,15 +1302,10 @@ public final class SessionImpl */ @Override public void evict(Object object) throws HibernateException { - fireEvict( new EvictEvent( object, this ) ); - } - - private void fireEvict(EvictEvent event) { checkOpen(); pulseTransactionCoordinator(); - for ( EvictEventListener listener : listeners( EventType.EVICT ) ) { - listener.onEvict( event ); - } + final EvictEvent event = new EvictEvent( object, this ); + fastSessionServices.eventListenerGroup_EVICT.fireEventOnEachListener( event, EvictEventListener::onEvict ); delayedAfterCompletion(); } @@ -1423,9 +1320,7 @@ public final class SessionImpl return false; } AutoFlushEvent event = new AutoFlushEvent( querySpaces, this ); - for ( AutoFlushEventListener listener : listeners( EventType.AUTO_FLUSH ) ) { - listener.onAutoFlush( event ); - } + fastSessionServices.eventListenerGroup_AUTO_FLUSH.fireEventOnEachListener( event, AutoFlushEventListener::onAutoFlush ); return event.isFlushRequired(); } @@ -1439,9 +1334,7 @@ public final class SessionImpl return true; } DirtyCheckEvent event = new DirtyCheckEvent( this ); - for ( DirtyCheckEventListener listener : listeners( EventType.DIRTY_CHECK ) ) { - listener.onDirtyCheck( event ); - } + fastSessionServices.eventListenerGroup_DIRTY_CHECK.fireEventOnEachListener( event, DirtyCheckEventListener::onDirtyCheck ); delayedAfterCompletion(); return event.isDirty(); } @@ -1461,11 +1354,8 @@ public final class SessionImpl throw new HibernateException( "Flush during cascade is dangerous" ); } - FlushEvent flushEvent = new FlushEvent( this ); - for ( FlushEventListener listener : listeners( EventType.FLUSH ) ) { - listener.onFlush( flushEvent ); - } - + FlushEvent event = new FlushEvent( this ); + fastSessionServices.eventListenerGroup_FLUSH.fireEventOnEachListener( event, FlushEventListener::onFlush ); delayedAfterCompletion(); } catch ( RuntimeException e ) { @@ -2266,9 +2156,7 @@ public final class SessionImpl checkOpenOrWaitingForAutoClose(); pulseTransactionCoordinator(); InitializeCollectionEvent event = new InitializeCollectionEvent( collection, this ); - for ( InitializeCollectionEventListener listener : listeners( EventType.INIT_COLLECTION ) ) { - listener.onInitializeCollection( event ); - } + fastSessionServices.eventListenerGroup_INIT_COLLECTION.fireEventOnEachListener( event, InitializeCollectionEventListener::onInitializeCollection ); delayedAfterCompletion(); } @@ -2506,12 +2394,12 @@ public final class SessionImpl // We do not want an exception to be thrown if the transaction // is not accessible. If the transaction is not accessible, // then return null. - return isTransactionAccessible() ? accessTransaction() : null; + return fastSessionServices.isJtaTransactionAccessible ? accessTransaction() : null; } @Override public void beforeTransactionCompletion() { - log.tracef( "SessionImpl#beforeTransactionCompletion()" ); + log.trace( "SessionImpl#beforeTransactionCompletion()" ); flushBeforeTransactionCompletion(); actionQueue.beforeTransactionCompletion(); try { @@ -2525,7 +2413,9 @@ public final class SessionImpl @Override public void afterTransactionCompletion(boolean successful, boolean delayed) { - log.tracef( "SessionImpl#afterTransactionCompletion(successful=%s, delayed=%s)", successful, delayed ); + if ( log.isTraceEnabled() ) { + log.tracef( "SessionImpl#afterTransactionCompletion(successful=%s, delayed=%s)", successful, delayed ); + } if ( !isClosed() || waitingForAutoClose ) { if ( autoClear ||!successful ) { @@ -3364,25 +3254,6 @@ public final class SessionImpl return getHibernateFlushMode() != FlushMode.MANUAL; } - private static final AfterCompletionAction STANDARD_AFTER_COMPLETION_ACTION = (AfterCompletionAction) (successful, session) -> { - // nothing to do by default. - }; - - - public static class ManagedFlushCheckerStandardImpl implements ManagedFlushChecker { - @Override - public boolean shouldDoManagedFlush(SessionImplementor session) { - if ( session.isClosed() ) { - return false; - } - return session.getHibernateFlushMode() != FlushMode.MANUAL; - } - } - - private static final ManagedFlushCheckerStandardImpl STANDARD_MANAGED_FLUSH_CHECKER = new ManagedFlushCheckerStandardImpl() { - }; - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // HibernateEntityManager impl @@ -3399,57 +3270,17 @@ public final class SessionImpl @Override public LockOptions getLockRequest(LockModeType lockModeType, Map properties) { LockOptions lockOptions = new LockOptions(); - LockOptions.copy( this.lockOptions, lockOptions ); + if ( this.lockOptions != null ) { //otherwise the default LockOptions constructor is the same as DEFAULT_LOCK_OPTIONS + LockOptions.copy( this.lockOptions, lockOptions ); + } lockOptions.setLockMode( LockModeTypeHelper.getLockMode( lockModeType ) ); if ( properties != null ) { - setLockOptions( properties, lockOptions ); + LockOptionsHelper.applyPropertiesToLockOptions( properties, () -> lockOptions ); } return lockOptions; } - private void setLockOptions(Map props, LockOptions options) { - Object lockScope = props.get( JPA_LOCK_SCOPE ); - if ( lockScope instanceof String && PessimisticLockScope.valueOf( ( String ) lockScope ) == PessimisticLockScope.EXTENDED ) { - options.setScope( true ); - } - else if ( lockScope instanceof PessimisticLockScope ) { - boolean extended = PessimisticLockScope.EXTENDED.equals( lockScope ); - options.setScope( extended ); - } - else if ( lockScope != null ) { - throw new PersistenceException( "Unable to parse " + JPA_LOCK_SCOPE + ": " + lockScope ); - } - Object lockTimeout = props.get( JPA_LOCK_TIMEOUT ); - int timeout = 0; - boolean timeoutSet = false; - if ( lockTimeout instanceof String ) { - timeout = Integer.parseInt( ( String ) lockTimeout ); - timeoutSet = true; - } - else if ( lockTimeout instanceof Number ) { - timeout = ( (Number) lockTimeout ).intValue(); - timeoutSet = true; - } - else if ( lockTimeout != null ) { - throw new PersistenceException( "Unable to parse " + JPA_LOCK_TIMEOUT + ": " + lockTimeout ); - } - - if ( timeoutSet ) { - if ( timeout == LockOptions.SKIP_LOCKED ) { - options.setTimeOut( LockOptions.SKIP_LOCKED ); - } - else if ( timeout < 0 ) { - options.setTimeOut( LockOptions.WAIT_FOREVER ); - } - else if ( timeout == 0 ) { - options.setTimeOut( LockOptions.NO_WAIT ); - } - else { - options.setTimeOut( timeout ); - } - } - } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -3555,20 +3386,20 @@ public final class SessionImpl } if ( retrieveMode == null ) { // use the EM setting - retrieveMode = determineCacheRetrieveMode( this.properties ); + retrieveMode = fastSessionServices.getCacheRetrieveMode( this.properties ); } if ( storeMode == null ) { // use the EM setting - storeMode = determineCacheStoreMode( this.properties ); + storeMode = fastSessionServices.getCacheStoreMode( this.properties ); } return CacheModeHelper.interpretCacheMode( storeMode, retrieveMode ); } - private CacheRetrieveMode determineCacheRetrieveMode(Map settings) { + private static CacheRetrieveMode determineCacheRetrieveMode(Map settings) { return ( CacheRetrieveMode ) settings.get( JPA_SHARED_CACHE_RETRIEVE_MODE ); } - private CacheStoreMode determineCacheStoreMode(Map settings) { + private static CacheStoreMode determineCacheStoreMode(Map settings) { return ( CacheStoreMode ) settings.get( JPA_SHARED_CACHE_STORE_MODE ); } @@ -3698,12 +3529,48 @@ public final class SessionImpl return; } + if ( propertyName == null ) { + log.warnf( "Property having key null is illegal; value won't be set." ); + return; + } + + //Store property for future reference: + + if ( properties == null ) { + properties = computeCurrentSessionProperties(); + } properties.put( propertyName, value ); - applyProperties(); + + //now actually update settings, if it's any of these which have a direct impact on this Session state: + + if ( AvailableSettings.FLUSH_MODE.equals( propertyName ) ) { + setHibernateFlushMode( ConfigurationHelper.getFlushMode( value, FlushMode.AUTO ) ); + } + else if ( JPA_LOCK_SCOPE.equals( propertyName ) || JPA_LOCK_TIMEOUT.equals( propertyName ) ) { + LockOptionsHelper.applyPropertiesToLockOptions( properties, this::getLockOptionsForWrite ); + } + else if ( JPA_SHARED_CACHE_RETRIEVE_MODE.equals( propertyName ) || JPA_SHARED_CACHE_STORE_MODE.equals( propertyName ) ) { + getSession().setCacheMode( + CacheModeHelper.interpretCacheMode( + determineCacheStoreMode( properties ), + determineCacheRetrieveMode( properties ) + ) + ); + } + } + + private Map computeCurrentSessionProperties() { + final HashMap map = new HashMap<>( fastSessionServices.defaultSessionProperties ); + //The FLUSH_MODE is always set at Session creation time, so it needs special treatment to not eagerly initialize this Map: + map.put( AvailableSettings.FLUSH_MODE, getHibernateFlushMode().name() ); + return map; } @Override public Map getProperties() { + if ( properties == null ) { + properties = computeCurrentSessionProperties(); + } return Collections.unmodifiableMap( properties ); } @@ -3926,7 +3793,5 @@ public final class SessionImpl for ( String filterName : loadQueryInfluencers.getEnabledFilterNames() ) { ( (FilterImpl) loadQueryInfluencers.getEnabledFilter( filterName ) ).afterDeserialize( getFactory() ); } - - this.discardOnClose = getFactory().getSessionFactoryOptions().isReleaseResourcesOnCloseEnabled(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/StringHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/StringHelper.java index 9c658ec37f..f79b03a651 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/StringHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/StringHelper.java @@ -123,7 +123,7 @@ public final class StringHelper { if ( template == null ) { return null; } - int loc = template.indexOf( placeholder ); + int loc = indexOfPlaceHolder( template, placeholder, wholeWords ); if ( loc < 0 ) { return template; } @@ -189,6 +189,24 @@ public final class StringHelper { return buf.toString(); } + private static int indexOfPlaceHolder(String template, String placeholder, boolean wholeWords) { + if ( wholeWords ) { + int placeholderIndex = -1; + boolean isPartialPlaceholderMatch; + do { + placeholderIndex = template.indexOf( placeholder, placeholderIndex + 1 ); + isPartialPlaceholderMatch = placeholderIndex != -1 && + template.length() > placeholderIndex + placeholder.length() && + Character.isJavaIdentifierPart( template.charAt( placeholderIndex + placeholder.length() ) ); + } while ( placeholderIndex != -1 && isPartialPlaceholderMatch ); + + return placeholderIndex; + } + else { + return template.indexOf( placeholder ); + } + } + /** * Used to find the ordinal parameters (e.g. '?1') in a string. */ diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/IdentityMap.java b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/IdentityMap.java index eeda7b1ab1..d64786bbaf 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/IdentityMap.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/IdentityMap.java @@ -13,6 +13,7 @@ import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; +import java.util.function.BiConsumer; import java.util.function.Consumer; /** @@ -62,6 +63,15 @@ public final class IdentityMap implements Map { identityMap.map.forEach( (kIdentityKey, v) -> consumer.accept( kIdentityKey.key ) ); } + /** + * Override Map{@link #forEach(BiConsumer)} to provide a more efficient implementation + * @param action the operation to apply to each element + */ + @Override + public void forEach(BiConsumer action) { + map.forEach( (k,v) -> action.accept( k.key, v ) ); + } + public Iterator keyIterator() { return new KeyIterator( map.keySet().iterator() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/internal/util/ConfigurationHelper.java b/hibernate-core/src/main/java/org/hibernate/jpa/internal/util/ConfigurationHelper.java index 8a66a509c7..ea78f317db 100644 --- a/hibernate-core/src/main/java/org/hibernate/jpa/internal/util/ConfigurationHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/internal/util/ConfigurationHelper.java @@ -30,21 +30,21 @@ public abstract class ConfigurationHelper { public static FlushMode getFlushMode(Object value, FlushMode defaultFlushMode) { final FlushMode flushMode; - if (value instanceof FlushMode) { + if ( value instanceof FlushMode ) { flushMode = (FlushMode) value; } - else if (value instanceof javax.persistence.FlushModeType) { - flushMode = ConfigurationHelper.getFlushMode( (javax.persistence.FlushModeType) value); + else if ( value instanceof javax.persistence.FlushModeType ) { + flushMode = ConfigurationHelper.getFlushMode( (javax.persistence.FlushModeType) value ); } - else if (value instanceof String) { - flushMode = ConfigurationHelper.getFlushMode( (String) value); + else if ( value instanceof String ) { + flushMode = ConfigurationHelper.getFlushMode( (String) value ); } else { flushMode = defaultFlushMode; } - if (flushMode == null) { - throw new PersistenceException("Unable to parse org.hibernate.flushMode: " + value); + if ( flushMode == null ) { + throw new PersistenceException( "Unable to parse org.hibernate.flushMode: " + value ); } return flushMode; @@ -55,21 +55,21 @@ public abstract class ConfigurationHelper { } private static FlushMode getFlushMode(String flushMode) { - if (flushMode == null) { + if ( flushMode == null ) { return null; } - flushMode = flushMode.toUpperCase(Locale.ROOT); + flushMode = flushMode.toUpperCase( Locale.ROOT ); return FlushMode.valueOf( flushMode ); } private static FlushMode getFlushMode(FlushModeType flushMode) { - switch(flushMode) { + switch ( flushMode ) { case AUTO: return FlushMode.AUTO; case COMMIT: return FlushMode.COMMIT; default: - throw new AssertionFailure("Unknown FlushModeType: " + flushMode); + throw new AssertionFailure( "Unknown FlushModeType: " + flushMode ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/internal/util/LockOptionsHelper.java b/hibernate-core/src/main/java/org/hibernate/jpa/internal/util/LockOptionsHelper.java new file mode 100644 index 0000000000..20e1ea641b --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/jpa/internal/util/LockOptionsHelper.java @@ -0,0 +1,77 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.internal.util; + +import java.util.Map; +import java.util.function.Supplier; +import javax.persistence.PersistenceException; +import javax.persistence.PessimisticLockScope; + +import org.hibernate.LockOptions; + +import static org.hibernate.cfg.AvailableSettings.JPA_LOCK_SCOPE; +import static org.hibernate.cfg.AvailableSettings.JPA_LOCK_TIMEOUT; + +public final class LockOptionsHelper { + + private LockOptionsHelper() { + //utility class, not to be constructed + } + + /** + * Applies configuration properties on a {@link LockOptions} instance, passed as a supplier + * so to make it possible to skip allocating the {@link LockOptions} instance if there's + * nothing to set. + * + * @param props The configuration properties + * @param lockOptionsSupplier The reference to the lock to modify + */ + public static void applyPropertiesToLockOptions(final Map props, final Supplier lockOptionsSupplier) { + Object lockScope = props.get( JPA_LOCK_SCOPE ); + if ( lockScope instanceof String && PessimisticLockScope.valueOf( (String) lockScope ) == PessimisticLockScope.EXTENDED ) { + lockOptionsSupplier.get().setScope( true ); + } + else if ( lockScope instanceof PessimisticLockScope ) { + boolean extended = PessimisticLockScope.EXTENDED.equals( lockScope ); + lockOptionsSupplier.get().setScope( extended ); + } + else if ( lockScope != null ) { + throw new PersistenceException( "Unable to parse " + JPA_LOCK_SCOPE + ": " + lockScope ); + } + + Object lockTimeout = props.get( JPA_LOCK_TIMEOUT ); + int timeout = 0; + boolean timeoutSet = false; + if ( lockTimeout instanceof String ) { + timeout = Integer.parseInt( (String) lockTimeout ); + timeoutSet = true; + } + else if ( lockTimeout instanceof Number ) { + timeout = ( (Number) lockTimeout ).intValue(); + timeoutSet = true; + } + else if ( lockTimeout != null ) { + throw new PersistenceException( "Unable to parse " + JPA_LOCK_TIMEOUT + ": " + lockTimeout ); + } + + if ( timeoutSet ) { + if ( timeout == LockOptions.SKIP_LOCKED ) { + lockOptionsSupplier.get().setTimeOut( LockOptions.SKIP_LOCKED ); + } + else if ( timeout < 0 ) { + lockOptionsSupplier.get().setTimeOut( LockOptions.WAIT_FOREVER ); + } + else if ( timeout == 0 ) { + lockOptionsSupplier.get().setTimeOut( LockOptions.NO_WAIT ); + } + else { + lockOptionsSupplier.get().setTimeOut( timeout ); + } + } + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AbstractLoadPlanBasedLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AbstractLoadPlanBasedLoader.java index eb5a52688a..eb320df4c7 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AbstractLoadPlanBasedLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/internal/AbstractLoadPlanBasedLoader.java @@ -11,7 +11,6 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; -import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; @@ -38,7 +37,6 @@ import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.loader.plan.exec.query.spi.NamedParameterContext; import org.hibernate.loader.plan.exec.spi.LoadQueryDetails; -import org.hibernate.loader.spi.AfterLoadAction; import org.hibernate.resource.jdbc.ResourceRegistry; import org.hibernate.transform.ResultTransformer; import org.hibernate.type.Type; @@ -138,13 +136,6 @@ public abstract class AbstractLoadPlanBasedLoader { } } - protected SqlStatementWrapper executeQueryStatement( - final QueryParameters queryParameters, - final boolean scroll, - final SharedSessionContractImplementor session) throws SQLException { - return executeQueryStatement( getStaticLoadQuery().getSqlStatement(), queryParameters, scroll, session ); - } - protected SqlStatementWrapper executeQueryStatement( String sqlStatement, QueryParameters queryParameters, diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/ResultSetProcessorImpl.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/ResultSetProcessorImpl.java index 886ce50149..81c32a0fae 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/ResultSetProcessorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/ResultSetProcessorImpl.java @@ -63,10 +63,6 @@ public class ResultSetProcessorImpl implements ResultSetProcessor { this.hadSubselectFetches = hadSubselectFetches; } - public RowReader getRowReader() { - return rowReader; - } - @Override public ScrollableResultSetProcessor toOnDemandForm() { // todo : implement @@ -86,13 +82,20 @@ public class ResultSetProcessorImpl implements ResultSetProcessor { handlePotentiallyEmptyCollectionRootReturns( loadPlan, queryParameters.getCollectionKeys(), resultSet, session ); + final boolean traceEnabled = LOG.isTraceEnabled(); final int maxRows; + final List loadResults; final RowSelection selection = queryParameters.getRowSelection(); if ( LimitHelper.hasMaxRows( selection ) ) { maxRows = selection.getMaxRows(); - LOG.tracef( "Limiting ResultSet processing to just %s rows", maxRows ); + if ( traceEnabled ) { + LOG.tracef( "Limiting ResultSet processing to just %s rows", maxRows ); + } + int sizeHint = maxRows < 50 ? maxRows : 50; + loadResults = new ArrayList( sizeHint ); } else { + loadResults = new ArrayList(); maxRows = Integer.MAX_VALUE; } @@ -113,12 +116,14 @@ public class ResultSetProcessorImpl implements ResultSetProcessor { hadSubselectFetches ); - final List loadResults = new ArrayList(); - - LOG.trace( "Processing result set" ); + if ( traceEnabled ) { + LOG.trace( "Processing result set" ); + } int count; for ( count = 0; count < maxRows && resultSet.next(); count++ ) { - LOG.debugf( "Starting ResultSet row #%s", count ); + if ( traceEnabled ) { + LOG.tracef( "Starting ResultSet row #%s", count ); + } Object logicalRow = rowReader.readRow( resultSet, context ); @@ -129,7 +134,9 @@ public class ResultSetProcessorImpl implements ResultSetProcessor { context.finishUpRow(); } - LOG.tracev( "Done processing result set ({0} rows)", count ); + if ( traceEnabled ) { + LOG.tracev( "Done processing result set ({0} rows)", count ); + } rowReader.finishUp( context, afterLoadActionList ); context.wrapUp(); diff --git a/hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java index 42380d1fe7..e7f9e2de1b 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java @@ -1915,8 +1915,9 @@ public abstract class AbstractCollectionPersister } String[] result = new String[rawAliases.length]; + final Alias alias = new Alias( suffix ); for ( int i = 0; i < rawAliases.length; i++ ) { - result[i] = new Alias( suffix ).toUnquotedAliasString( rawAliases[i] ); + result[i] = alias.toUnquotedAliasString( rawAliases[i] ); } return result; } 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 8f41e64aa8..7fea63ab50 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 @@ -533,6 +533,14 @@ public abstract class AbstractEntityPersister return propertySelectable; } + public String[] getTableNames() { + String[] tableNames = new String[getTableSpan()]; + for ( int i = 0; i < tableNames.length; i++ ) { + tableNames[i] = getTableName( i ); + } + return tableNames; + } + @SuppressWarnings("UnnecessaryBoxing") public AbstractEntityPersister( final PersistentClass persistentClass, @@ -560,7 +568,7 @@ public abstract class AbstractEntityPersister this.naturalIdRegionAccessStrategy = null; } - this.entityMetamodel = new EntityMetamodel( persistentClass, this, creationContext ); + this.entityMetamodel = new EntityMetamodel( persistentClass, this, factory ); this.entityTuplizer = this.entityMetamodel.getTuplizer(); if ( entityMetamodel.isMutable() ) { @@ -696,14 +704,7 @@ public abstract class AbstractEntityPersister final boolean lazy = ! EnhancementHelper.includeInBaseFetchGroup( prop, entityMetamodel.isInstrumented(), - creationContext.getSessionFactory().getSessionFactoryOptions().isEnhancementAsProxyEnabled(), - associatedEntityName -> { - final PersistentClass bootEntityDescriptor = creationContext.getMetadata().getEntityBinding( associatedEntityName ); - if ( bootEntityDescriptor == null ) { - return false; - } - return bootEntityDescriptor.hasSubclasses(); - } + creationContext.getSessionFactory().getSessionFactoryOptions().isEnhancementAsProxyEnabled() ); if ( lazy ) { @@ -779,14 +780,7 @@ public abstract class AbstractEntityPersister final boolean lazy = ! EnhancementHelper.includeInBaseFetchGroup( prop, entityMetamodel.isInstrumented(), - creationContext.getSessionFactory().getSessionFactoryOptions().isEnhancementAsProxyEnabled(), - associatedEntityName -> { - final PersistentClass bootEntityDescriptor = creationContext.getMetadata().getEntityBinding( associatedEntityName ); - if ( bootEntityDescriptor == null ) { - return false; - } - return bootEntityDescriptor.hasSubclasses(); - } + creationContext.getSessionFactory().getSessionFactoryOptions().isEnhancementAsProxyEnabled() ); while ( colIter.hasNext() ) { Selectable thing = (Selectable) colIter.next(); @@ -3890,7 +3884,8 @@ public abstract class AbstractEntityPersister alias, innerJoin, includeSubclasses, - Collections.emptySet() + Collections.emptySet(), + null ).toFromFragmentString(); } @@ -3903,7 +3898,19 @@ public abstract class AbstractEntityPersister // NOTE : Not calling createJoin here is just a performance optimization return getSubclassTableSpan() == 1 ? "" - : createJoin( alias, innerJoin, includeSubclasses, treatAsDeclarations ).toFromFragmentString(); + : createJoin( alias, innerJoin, includeSubclasses, treatAsDeclarations, null ).toFromFragmentString(); + } + + @Override + public String fromJoinFragment( + String alias, + boolean innerJoin, + boolean includeSubclasses, + Set treatAsDeclarations, + Set referencedTables) { + return getSubclassTableSpan() == 1 + ? "" + : createJoin( alias, innerJoin, includeSubclasses, treatAsDeclarations, referencedTables ).toFromFragmentString(); } @Override @@ -3915,7 +3922,8 @@ public abstract class AbstractEntityPersister alias, innerJoin, includeSubclasses, - Collections.emptySet() + Collections.emptySet(), + null ).toWhereFragmentString(); } @@ -3928,7 +3936,7 @@ public abstract class AbstractEntityPersister // NOTE : Not calling createJoin here is just a performance optimization return getSubclassTableSpan() == 1 ? "" - : createJoin( alias, innerJoin, includeSubclasses, treatAsDeclarations ).toWhereFragmentString(); + : createJoin( alias, innerJoin, includeSubclasses, treatAsDeclarations, null ).toWhereFragmentString(); } protected boolean isSubclassTableLazy(int j) { @@ -3940,6 +3948,15 @@ public abstract class AbstractEntityPersister boolean innerJoin, boolean includeSubclasses, Set treatAsDeclarations) { + return createJoin(name, innerJoin, includeSubclasses, treatAsDeclarations, null); + } + + protected JoinFragment createJoin( + String name, + boolean innerJoin, + boolean includeSubclasses, + Set treatAsDeclarations, + Set referencedTables) { // IMPL NOTE : all joins join to the pk of the driving table final String[] idCols = StringHelper.qualify( name, getIdentifierColumnNames() ); final JoinFragment join = getFactory().getDialect().createOuterJoinFragment(); @@ -3950,7 +3967,8 @@ public abstract class AbstractEntityPersister j, innerJoin, includeSubclasses, - treatAsDeclarations + treatAsDeclarations, + referencedTables ); if ( joinType != null && joinType != JoinType.NONE ) { @@ -3971,8 +3989,28 @@ public abstract class AbstractEntityPersister boolean canInnerJoin, boolean includeSubclasses, Set treatAsDeclarations) { + return determineSubclassTableJoinType( + subclassTableNumber, + canInnerJoin, + includeSubclasses, + treatAsDeclarations, + null + ); + } + + protected JoinType determineSubclassTableJoinType( + int subclassTableNumber, + boolean canInnerJoin, + boolean includeSubclasses, + Set treatAsDeclarations, + Set referencedTables) { if ( isClassOrSuperclassTable( subclassTableNumber ) ) { + String superclassTableName = getSubclassTableName( subclassTableNumber ); + if ( referencedTables != null && canOmitSuperclassTableJoin() && !referencedTables.contains( + superclassTableName ) ) { + return JoinType.NONE; + } final boolean shouldInnerJoin = canInnerJoin && !isInverseTable( subclassTableNumber ) && !isNullableTable( subclassTableNumber ); @@ -5212,8 +5250,8 @@ public abstract class AbstractEntityPersister if ( attribute.getType() instanceof ComponentType ) { final ComponentType type = (ComponentType) attribute.getType(); final ValueGeneration[] propertyValueGenerationStrategies = type.getPropertyValueGenerationStrategies(); - for ( int i = 0; i < propertyValueGenerationStrategies.length; i++ ) { - if ( isReadRequired( propertyValueGenerationStrategies[i], matchTiming ) ) { + for ( ValueGeneration propertyValueGenerationStrategie : propertyValueGenerationStrategies ) { + if ( isReadRequired( propertyValueGenerationStrategie, matchTiming ) ) { return true; } } @@ -5735,6 +5773,15 @@ public abstract class AbstractEntityPersister return ArrayHelper.to2DStringArray( polymorphicJoinColumns ); } + /** + * If true, persister can omit superclass tables during joining if they are not needed in the query. + * + * @return true if the persister can do it + */ + public boolean canOmitSuperclassTableJoin() { + return false; + } + private void prepareEntityIdentifierDefinition() { if ( entityIdentifierDefinition != null ) { return; diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/Joinable.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/Joinable.java index 2be118ad2c..aef89f331e 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/Joinable.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/Joinable.java @@ -57,6 +57,19 @@ public interface Joinable { */ public String fromJoinFragment(String alias, boolean innerJoin, boolean includeSubclasses, Set treatAsDeclarations); + /** + * Get the from clause part of any joins + * (optional operation) + */ + default String fromJoinFragment( + String alias, + boolean innerJoin, + boolean includeSubclasses, + Set treatAsDeclarations, + Set referencedTables) { + return fromJoinFragment( alias, innerJoin, includeSubclasses, treatAsDeclarations ); + } + /** * The columns to join on */ 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 02b2f7b6f5..6930d7ffea 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 @@ -1053,6 +1053,7 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister { return subclassNamesBySubclassTable[index]; } + @Override public String getPropertyTableName(String propertyName) { Integer index = getEntityMetamodel().getPropertyIndexOrNull( propertyName ); if ( index == null ) { @@ -1118,4 +1119,9 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister { public FilterAliasGenerator getFilterAliasGenerator(String rootAlias) { return new DynamicFilterAliasGenerator(subclassTableNameClosure, rootAlias); } + + @Override + public boolean canOmitSuperclassTableJoin() { + return true; + } } 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 c6bd95ada1..2ab666d8c2 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 @@ -809,6 +809,7 @@ public class SingleTableEntityPersister extends AbstractEntityPersister { return isNullableSubclassTable[j]; } + @Override public String getPropertyTableName(String propertyName) { Integer index = getEntityMetamodel().getPropertyIndexOrNull( propertyName ); if ( index == null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/UnionSubclassEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/UnionSubclassEntityPersister.java index 04f72dad9d..090d00a001 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/UnionSubclassEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/UnionSubclassEntityPersister.java @@ -466,6 +466,7 @@ public class UnionSubclassEntityPersister extends AbstractEntityPersister { return true; } + @Override public String getPropertyTableName(String propertyName) { //TODO: check this.... return getTableName(); @@ -483,4 +484,5 @@ public class UnionSubclassEntityPersister extends AbstractEntityPersister { public FilterAliasGenerator getFilterAliasGenerator(String rootAlias) { return new StaticFilterAliasGenerator( rootAlias ); } + } diff --git a/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/LogicalConnectionManagedImpl.java b/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/LogicalConnectionManagedImpl.java index 18b5774ece..3486a39fc2 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/LogicalConnectionManagedImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/LogicalConnectionManagedImpl.java @@ -48,20 +48,17 @@ public class LogicalConnectionManagedImpl extends AbstractLogicalConnectionImple public LogicalConnectionManagedImpl( JdbcConnectionAccess jdbcConnectionAccess, JdbcSessionContext jdbcSessionContext, - ResourceRegistry resourceRegistry) { + ResourceRegistry resourceRegistry, + JdbcServices jdbcServices) { this.jdbcConnectionAccess = jdbcConnectionAccess; this.observer = jdbcSessionContext.getObserver(); this.resourceRegistry = resourceRegistry; this.connectionHandlingMode = determineConnectionHandlingMode( jdbcSessionContext.getPhysicalConnectionHandlingMode(), - jdbcConnectionAccess + jdbcConnectionAccess ); - ); - - this.sqlExceptionHelper = jdbcSessionContext.getServiceRegistry() - .getService( JdbcServices.class ) - .getSqlExceptionHelper(); + this.sqlExceptionHelper = jdbcServices.getSqlExceptionHelper(); if ( connectionHandlingMode.getAcquisitionMode() == ConnectionAcquisitionMode.IMMEDIATELY ) { acquireConnectionIfNeeded(); @@ -94,14 +91,15 @@ public class LogicalConnectionManagedImpl extends AbstractLogicalConnectionImple JdbcConnectionAccess jdbcConnectionAccess, JdbcSessionContext jdbcSessionContext, boolean closed) { - this( jdbcConnectionAccess, jdbcSessionContext, new ResourceRegistryStandardImpl() ); + this( jdbcConnectionAccess, jdbcSessionContext, new ResourceRegistryStandardImpl(), + jdbcSessionContext.getServiceRegistry().getService( JdbcServices.class ) + ); this.closed = closed; } private Connection acquireConnectionIfNeeded() { if ( physicalConnection == null ) { // todo : is this the right place for these observer calls? - observer.jdbcConnectionAcquisitionStart(); try { physicalConnection = jdbcConnectionAccess.obtainConnection(); } @@ -187,8 +185,6 @@ public class LogicalConnectionManagedImpl extends AbstractLogicalConnectionImple return; } - // todo : is this the right place for these observer calls? - observer.jdbcConnectionReleaseStart(); try { if ( !physicalConnection.isClosed() ) { sqlExceptionHelper.logAndClearWarnings( physicalConnection ); @@ -221,7 +217,7 @@ public class LogicalConnectionManagedImpl extends AbstractLogicalConnectionImple public static LogicalConnectionManagedImpl deserialize( ObjectInputStream ois, JdbcConnectionAccess jdbcConnectionAccess, - JdbcSessionContext jdbcSessionContext) throws IOException, ClassNotFoundException { + JdbcSessionContext jdbcSessionContext) throws IOException { final boolean isClosed = ois.readBoolean(); return new LogicalConnectionManagedImpl( jdbcConnectionAccess, jdbcSessionContext, isClosed ); } diff --git a/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/ResourceRegistryStandardImpl.java b/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/ResourceRegistryStandardImpl.java index e78f1590e3..3f3af3b97f 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/ResourceRegistryStandardImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/ResourceRegistryStandardImpl.java @@ -13,13 +13,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; import org.hibernate.HibernateException; import org.hibernate.JDBCException; @@ -29,19 +23,40 @@ import org.hibernate.resource.jdbc.ResourceRegistry; import org.hibernate.resource.jdbc.spi.JdbcObserver; /** + * Helps to track statements and resultsets which need being closed. + * This class is not threadsafe. + * + * Note regarding performance: we had evidence that allocating Iterators + * to implement the cleanup on each element recursively was the dominant + * resource cost, so we decided using "forEach" and lambdas in this case. + * However the forEach/lambda combination is able to dodge allocating + * Iterators on HashMap and ArrayList, but not on HashSet (at least on JDK8 and 11). + * Therefore some types which should ideally be modelled as a Set have + * been implemented using HashMap. + * * @author Steve Ebersole + * @author Sanne Grinovero */ -public class ResourceRegistryStandardImpl implements ResourceRegistry { +public final class ResourceRegistryStandardImpl implements ResourceRegistry { + private static final CoreMessageLogger log = CoreLogging.messageLogger( ResourceRegistryStandardImpl.class ); + // Dummy value to associate with an Object in the backing Map when we use it as a set: + private static final Object PRESENT = new Object(); + + //Used instead of Collections.EMPTY_SET to avoid polymorhic calls on xref; + //Also, uses an HashMap as it were an HashSet, as technically we just need the Set semantics + //but in this case the overhead of HashSet is not negligible. + private static final HashMap EMPTY = new HashMap( 1, 0.2f ); + private final JdbcObserver jdbcObserver; - private final Map> xref = new HashMap>(); - private final Set unassociatedResultSets = new HashSet(); + private final HashMap> xref = new HashMap<>(); + private final HashMap unassociatedResultSets = new HashMap(); - private List blobs; - private List clobs; - private List nclobs; + private ArrayList blobs; + private ArrayList clobs; + private ArrayList nclobs; private Statement lastQuery; @@ -67,7 +82,7 @@ public class ResourceRegistryStandardImpl implements ResourceRegistry { public void register(Statement statement, boolean cancelable) { log.tracef( "Registering statement [%s]", statement ); - Set previousValue = xref.putIfAbsent( statement, Collections.EMPTY_SET ); + HashMap previousValue = xref.putIfAbsent( statement, EMPTY ); if ( previousValue != null ) { throw new HibernateException( "JDBC Statement already registered" ); } @@ -81,7 +96,7 @@ public class ResourceRegistryStandardImpl implements ResourceRegistry { public void release(Statement statement) { log.tracev( "Releasing statement [{0}]", statement ); - final Set resultSets = xref.remove( statement ); + final HashMap resultSets = xref.remove( statement ); if ( resultSets != null ) { closeAll( resultSets ); } @@ -111,7 +126,7 @@ public class ResourceRegistryStandardImpl implements ResourceRegistry { } } if ( statement != null ) { - final Set resultSets = xref.get( statement ); + final HashMap resultSets = xref.get( statement ); if ( resultSets == null ) { log.unregisteredStatement(); } @@ -123,24 +138,26 @@ public class ResourceRegistryStandardImpl implements ResourceRegistry { } } else { - final boolean removed = unassociatedResultSets.remove( resultSet ); - if ( !removed ) { + final Object removed = unassociatedResultSets.remove( resultSet ); + if ( removed == null ) { log.unregisteredResultSetWithoutStatement(); } - } close( resultSet ); } - protected void closeAll(Set resultSets) { - for ( ResultSet resultSet : resultSets ) { - close( resultSet ); - } + private static void closeAll(final HashMap resultSets) { + resultSets.forEach( (resultSet, o) -> close( resultSet ) ); resultSets.clear(); } + private static void releaseXref(final Statement s, final HashMap r) { + closeAll( r ); + close( s ); + } + @SuppressWarnings({"unchecked"}) - public static void close(ResultSet resultSet) { + private static void close(final ResultSet resultSet) { log.tracef( "Closing result set [%s]", resultSet ); try { @@ -202,7 +219,7 @@ public class ResourceRegistryStandardImpl implements ResourceRegistry { } } if ( statement != null ) { - Set resultSets = xref.get( statement ); + HashMap resultSets = xref.get( statement ); // Keep this at DEBUG level, rather than warn. Numerous connection pool implementations can return a // proxy/wrapper around the JDBC Statement, causing excessive logging here. See HHH-8210. @@ -210,14 +227,14 @@ public class ResourceRegistryStandardImpl implements ResourceRegistry { log.debug( "ResultSet statement was not registered (on register)" ); } - if ( resultSets == null || resultSets == Collections.EMPTY_SET ) { - resultSets = new HashSet(); + if ( resultSets == null || resultSets == EMPTY ) { + resultSets = new HashMap(); xref.put( statement, resultSets ); } - resultSets.add( resultSet ); + resultSets.put( resultSet, PRESENT ); } else { - unassociatedResultSets.add( resultSet ); + unassociatedResultSets.put( resultSet, PRESENT ); } } @@ -303,62 +320,54 @@ public class ResourceRegistryStandardImpl implements ResourceRegistry { jdbcObserver.jdbcReleaseRegistryResourcesStart(); } - for ( Map.Entry> entry : xref.entrySet() ) { - if ( entry.getValue() != null ) { - closeAll( entry.getValue() ); - } - close( entry.getKey() ); - } + xref.forEach( ResourceRegistryStandardImpl::releaseXref ); xref.clear(); closeAll( unassociatedResultSets ); if ( blobs != null ) { - for ( Blob blob : blobs ) { + blobs.forEach( blob -> { try { blob.free(); } catch (SQLException e) { log.debugf( "Unable to free JDBC Blob reference [%s]", e.getMessage() ); } - } - blobs.clear(); + } ); + //for these, it seems better to null the map rather than clear it: + blobs = null; } if ( clobs != null ) { - for ( Clob clob : clobs ) { + clobs.forEach( clob -> { try { clob.free(); } catch (SQLException e) { log.debugf( "Unable to free JDBC Clob reference [%s]", e.getMessage() ); } - } - clobs.clear(); + } ); + clobs = null; } if ( nclobs != null ) { - for ( NClob nclob : nclobs ) { + nclobs.forEach( nclob -> { try { nclob.free(); } catch (SQLException e) { log.debugf( "Unable to free JDBC NClob reference [%s]", e.getMessage() ); } - } - nclobs.clear(); - } - - if ( jdbcObserver != null ) { - jdbcObserver.jdbcReleaseRegistryResourcesEnd(); + } ); + nclobs = null; } } - private boolean hasRegistered(Map resource) { + private boolean hasRegistered(final HashMap resource) { return resource != null && !resource.isEmpty(); } - private boolean hasRegistered(Collection resource) { + private boolean hasRegistered(final ArrayList resource) { return resource != null && !resource.isEmpty(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/resource/jdbc/spi/JdbcObserver.java b/hibernate-core/src/main/java/org/hibernate/resource/jdbc/spi/JdbcObserver.java index 49aa95db79..124d28921d 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/jdbc/spi/JdbcObserver.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/jdbc/spi/JdbcObserver.java @@ -9,8 +9,11 @@ package org.hibernate.resource.jdbc.spi; import java.sql.Connection; /** + * @deprecated It is no longer possible to plug custom implementations of + * this SPI. It will be removed. * @author Steve Ebersole */ +@Deprecated public interface JdbcObserver { public void jdbcConnectionAcquisitionStart(); public void jdbcConnectionAcquisitionEnd(Connection connection); diff --git a/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jdbc/internal/JdbcResourceLocalTransactionCoordinatorImpl.java b/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jdbc/internal/JdbcResourceLocalTransactionCoordinatorImpl.java index 1bbde93579..a637c01c19 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jdbc/internal/JdbcResourceLocalTransactionCoordinatorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jdbc/internal/JdbcResourceLocalTransactionCoordinatorImpl.java @@ -7,6 +7,7 @@ package org.hibernate.resource.transaction.backend.jdbc.internal; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import javax.persistence.RollbackException; import javax.transaction.Status; @@ -50,7 +51,7 @@ public class JdbcResourceLocalTransactionCoordinatorImpl implements TransactionC private int timeOut = -1; - private final transient List observers; + private transient List observers = null; /** * Construct a ResourceLocalTransactionCoordinatorImpl instance. package-protected to ensure access goes through @@ -62,7 +63,6 @@ public class JdbcResourceLocalTransactionCoordinatorImpl implements TransactionC TransactionCoordinatorBuilder transactionCoordinatorBuilder, TransactionCoordinatorOwner owner, JdbcResourceTransactionAccess jdbcResourceTransactionAccess) { - this.observers = new ArrayList<>(); this.transactionCoordinatorBuilder = transactionCoordinatorBuilder; this.jdbcResourceTransactionAccess = jdbcResourceTransactionAccess; this.transactionCoordinatorOwner = owner; @@ -81,7 +81,12 @@ public class JdbcResourceLocalTransactionCoordinatorImpl implements TransactionC * @return TransactionObserver */ private Iterable observers() { - return new ArrayList<>( observers ); + if ( observers == null || observers.isEmpty() ) { + return Collections.emptyList(); + } + else { + return new ArrayList<>( observers ); + } } @Override @@ -203,12 +208,17 @@ public class JdbcResourceLocalTransactionCoordinatorImpl implements TransactionC @Override public void addObserver(TransactionObserver observer) { + if ( observers == null ) { + observers = new ArrayList<>( 6 ); + } observers.add( observer ); } @Override public void removeObserver(TransactionObserver observer) { - observers.remove( observer ); + if ( observers != null ) { + observers.remove( observer ); + } } /** diff --git a/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jta/internal/JtaTransactionCoordinatorImpl.java b/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jta/internal/JtaTransactionCoordinatorImpl.java index b2f3a59eae..13f2edaba5 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jta/internal/JtaTransactionCoordinatorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jta/internal/JtaTransactionCoordinatorImpl.java @@ -62,7 +62,7 @@ public class JtaTransactionCoordinatorImpl implements TransactionCoordinator, Sy private int timeOut = -1; - private final transient List observers; + private transient List observers = null; /** * Construct a JtaTransactionCoordinatorImpl instance. package-protected to ensure access goes through @@ -79,8 +79,6 @@ public class JtaTransactionCoordinatorImpl implements TransactionCoordinator, Sy this.transactionCoordinatorOwner = owner; this.autoJoinTransactions = autoJoinTransactions; - this.observers = new ArrayList<>(); - final JdbcSessionContext jdbcSessionContext = owner.getJdbcSessionOwner().getJdbcSessionContext(); this.jtaPlatform = jdbcSessionContext.getServiceRegistry().getService( JtaPlatform.class ); @@ -109,9 +107,8 @@ public class JtaTransactionCoordinatorImpl implements TransactionCoordinator, Sy this.preferUserTransactions = preferUserTransactions; this.performJtaThreadTracking = performJtaThreadTracking; - this.observers = new ArrayList<>(); - if ( observers != null ) { + this.observers = new ArrayList<>( observers.length ); Collections.addAll( this.observers, observers ); } @@ -123,11 +120,17 @@ public class JtaTransactionCoordinatorImpl implements TransactionCoordinator, Sy /** * Needed because while iterating the observers list and executing the before/update callbacks, * some observers might get removed from the list. + * Yet try to not allocate anything for when the list is empty, as this is a common case. * * @return TransactionObserver */ private Iterable observers() { - return new ArrayList<>( observers ); + if ( this.observers == null ) { + return Collections.EMPTY_LIST; + } + else { + return new ArrayList<>( this.observers ); + } } public SynchronizationCallbackCoordinator getSynchronizationCallbackCoordinator() { @@ -388,12 +391,17 @@ public class JtaTransactionCoordinatorImpl implements TransactionCoordinator, Sy } public void addObserver(TransactionObserver observer) { + if ( this.observers == null ) { + this.observers = new ArrayList<>( 3 ); //These lists are typically very small. + } observers.add( observer ); } @Override public void removeObserver(TransactionObserver observer) { - observers.remove( observer ); + if ( observers != null ) { + observers.remove( observer ); + } } /** diff --git a/hibernate-core/src/main/java/org/hibernate/sql/Insert.java b/hibernate-core/src/main/java/org/hibernate/sql/Insert.java index 21bae46d67..25b2d7d276 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/Insert.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/Insert.java @@ -38,12 +38,12 @@ public class Insert { } public Insert addColumn(String columnName) { - return addColumn(columnName, "?"); + return addColumn( columnName, "?" ); } public Insert addColumns(String[] columnNames) { - for ( int i=0; i hasSubclassChecker) { + boolean lazyAvailable) { final Type type = property.getValue().getType(); final NonIdentifierAttributeNature nature = decode( type ); @@ -174,8 +172,7 @@ public final class PropertyFactory { final boolean lazy = ! EnhancementHelper.includeInBaseFetchGroup( property, lazyAvailable, - sessionFactory.getSessionFactoryOptions().isEnhancementAsProxyEnabled(), - hasSubclassChecker + sessionFactory.getSessionFactoryOptions().isEnhancementAsProxyEnabled() ); switch ( nature ) { diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/BytecodeEnhancementMetadataPojoImpl.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/BytecodeEnhancementMetadataPojoImpl.java index 390db7b179..cad2b64c9f 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/BytecodeEnhancementMetadataPojoImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/BytecodeEnhancementMetadataPojoImpl.java @@ -8,7 +8,6 @@ package org.hibernate.tuple.entity; import java.io.Serializable; import java.util.Set; -import java.util.function.Function; import org.hibernate.LockMode; import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; @@ -38,12 +37,11 @@ public final class BytecodeEnhancementMetadataPojoImpl implements BytecodeEnhanc PersistentClass persistentClass, Set identifierAttributeNames, CompositeType nonAggregatedCidMapper, - boolean allowEnhancementAsProxy, - Function hasSubclassChecker) { + boolean allowEnhancementAsProxy) { final Class mappedClass = persistentClass.getMappedClass(); final boolean enhancedForLazyLoading = PersistentAttributeInterceptable.class.isAssignableFrom( mappedClass ); final LazyAttributesMetadata lazyAttributesMetadata = enhancedForLazyLoading - ? LazyAttributesMetadata.from( persistentClass, true, allowEnhancementAsProxy, hasSubclassChecker ) + ? LazyAttributesMetadata.from( persistentClass, true, allowEnhancementAsProxy ) : LazyAttributesMetadata.nonEnhanced( persistentClass.getEntityName() ); return new BytecodeEnhancementMetadataPojoImpl( 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 f4290487f7..298cd24482 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 @@ -33,7 +33,6 @@ import org.hibernate.mapping.Component; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.persister.spi.PersisterCreationContext; import org.hibernate.tuple.GenerationTiming; import org.hibernate.tuple.IdentifierProperty; import org.hibernate.tuple.InDatabaseValueGenerationStrategy; @@ -128,8 +127,8 @@ public class EntityMetamodel implements Serializable { public EntityMetamodel( PersistentClass persistentClass, EntityPersister persister, - final PersisterCreationContext creationContext) { - this.sessionFactory = creationContext.getSessionFactory(); + SessionFactoryImplementor sessionFactory) { + this.sessionFactory = sessionFactory; name = persistentClass.getEntityName(); rootName = persistentClass.getRootClass().getEntityName(); @@ -164,14 +163,7 @@ public class EntityMetamodel implements Serializable { persistentClass, idAttributeNames, nonAggregatedCidMapper, - sessionFactory.getSessionFactoryOptions().isEnhancementAsProxyEnabled(), - associatedEntityName -> { - final PersistentClass bootEntityDescriptor = creationContext.getMetadata().getEntityBinding( associatedEntityName ); - if ( bootEntityDescriptor == null ) { - return false; - } - return bootEntityDescriptor.hasSubclasses(); - } + sessionFactory.getSessionFactoryOptions().isEnhancementAsProxyEnabled() ); } else { @@ -234,14 +226,7 @@ public class EntityMetamodel implements Serializable { sessionFactory, i, prop, - bytecodeEnhancementMetadata.isEnhancedForLazyLoading(), - associatedEntityName -> { - final PersistentClass bootEntityDescriptor = creationContext.getMetadata().getEntityBinding( associatedEntityName ); - if ( bootEntityDescriptor == null ) { - return false; - } - return bootEntityDescriptor.hasSubclasses(); - } + bytecodeEnhancementMetadata.isEnhancedForLazyLoading() ); } @@ -260,14 +245,7 @@ public class EntityMetamodel implements Serializable { boolean lazy = ! EnhancementHelper.includeInBaseFetchGroup( prop, bytecodeEnhancementMetadata.isEnhancedForLazyLoading(), - sessionFactory.getSessionFactoryOptions().isEnhancementAsProxyEnabled(), - associatedEntityName -> { - final PersistentClass bootEntityDescriptor = creationContext.getMetadata().getEntityBinding( associatedEntityName ); - if ( bootEntityDescriptor == null ) { - return false; - } - return bootEntityDescriptor.hasSubclasses(); - } + sessionFactory.getSessionFactoryOptions().isEnhancementAsProxyEnabled() ); if ( lazy ) { @@ -832,8 +810,8 @@ public class EntityMetamodel implements Serializable { } else if ( type.isComponentType() ) { Type[] subtypes = ( (CompositeType) type ).getSubtypes(); - for ( int i = 0; i < subtypes.length; i++ ) { - if ( indicatesCollection( subtypes[i] ) ) { + for ( Type subtype : subtypes ) { + if ( indicatesCollection( subtype ) ) { return true; } } diff --git a/hibernate-core/src/test/java/org/hibernate/boot/registry/classloading/internal/ClassLoaderServiceImplTest.java b/hibernate-core/src/test/java/org/hibernate/boot/registry/classloading/internal/ClassLoaderServiceImplTest.java index 78a7614108..14dfa58064 100644 --- a/hibernate-core/src/test/java/org/hibernate/boot/registry/classloading/internal/ClassLoaderServiceImplTest.java +++ b/hibernate-core/src/test/java/org/hibernate/boot/registry/classloading/internal/ClassLoaderServiceImplTest.java @@ -8,8 +8,11 @@ package org.hibernate.boot.registry.classloading.internal; import java.net.URL; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.List; +import org.hibernate.testing.TestForIssue; import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -124,6 +127,28 @@ public class ClassLoaderServiceImplTest { csi.stop(); } + @Test + @TestForIssue(jiraKey = "HHH-13551") + public void testServiceFromIncompatibleClassLoader() { + ClassLoaderServiceImpl classLoaderService = new ClassLoaderServiceImpl( + Arrays.asList( + getClass().getClassLoader(), + /* + * This classloader will return instances of MyService where MyService + * is a different object than the one we manipulate in the current classloader. + * This used to throw an exception that triggered a boot failure in ORM, + * but should now be ignored. + */ + new IsolatedClassLoader( getClass().getClassLoader() ) + ), + TcclLookupPrecedence.AFTER + ); + + Collection loadedServices = classLoaderService.loadJavaServices( MyService.class ); + + assertEquals( 1, loadedServices.size() ); + } + private static class InternalClassLoader extends ClassLoader { private List names = new ArrayList<>( ); diff --git a/hibernate-core/src/test/java/org/hibernate/boot/registry/classloading/internal/IsolatedClassLoader.java b/hibernate-core/src/test/java/org/hibernate/boot/registry/classloading/internal/IsolatedClassLoader.java new file mode 100644 index 0000000000..a1a1a6d5c9 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/boot/registry/classloading/internal/IsolatedClassLoader.java @@ -0,0 +1,68 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.boot.registry.classloading.internal; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Enumeration; + +import org.hibernate.bytecode.spi.ByteCodeHelper; + +/** + * A classloader isolated from the application classloader, + * simulating a separate classloader that can create duplicate classes. + * This can result in a Service implementation extending + * a Service interface that is essentially the same as the one manipulated by ORM, + * but is a different Class instance and is thus deemed different by the JVM. + */ +class IsolatedClassLoader extends ClassLoader { + /** + * Another classloader from which resources will be read. + * Classes available in that classloader will be duplicated in the isolated classloader. + */ + private final ClassLoader resourceSource; + + IsolatedClassLoader(ClassLoader resourceSource) { + super( getTopLevelClassLoader( resourceSource ) ); + this.resourceSource = resourceSource; + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + InputStream is = getResourceAsStream( name.replace( '.', '/' ) + ".class" ); + if ( is == null ) { + throw new ClassNotFoundException( name + " not found" ); + } + + try { + byte[] bytecode = ByteCodeHelper.readByteCode( is ); + return defineClass( name, bytecode, 0, bytecode.length ); + } + catch( Throwable t ) { + throw new ClassNotFoundException( name + " not found", t ); + } + } + + @Override + public URL getResource(String name) { + return resourceSource.getResource( name ); + } + + @Override + public Enumeration getResources(String name) throws IOException { + return resourceSource.getResources( name ); + } + + private static ClassLoader getTopLevelClassLoader(ClassLoader classFileSource) { + ClassLoader result = classFileSource; + while ( result.getParent() != null ) { + result = result.getParent(); + } + return result; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/boot/registry/classloading/internal/MyService.java b/hibernate-core/src/test/java/org/hibernate/boot/registry/classloading/internal/MyService.java new file mode 100644 index 0000000000..ce0898803d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/boot/registry/classloading/internal/MyService.java @@ -0,0 +1,12 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.boot.registry.classloading.internal; + +import org.hibernate.service.Service; + +public interface MyService extends Service { +} diff --git a/hibernate-core/src/test/java/org/hibernate/boot/registry/classloading/internal/MyServiceImpl.java b/hibernate-core/src/test/java/org/hibernate/boot/registry/classloading/internal/MyServiceImpl.java new file mode 100644 index 0000000000..fa9f185022 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/boot/registry/classloading/internal/MyServiceImpl.java @@ -0,0 +1,10 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.boot.registry.classloading.internal; + +public class MyServiceImpl implements MyService { +} diff --git a/hibernate-core/src/test/java/org/hibernate/engine/jdbc/internal/JdbcCoordinatorTest.java b/hibernate-core/src/test/java/org/hibernate/engine/jdbc/internal/JdbcCoordinatorTest.java index df1646aa58..50a8334265 100644 --- a/hibernate-core/src/test/java/org/hibernate/engine/jdbc/internal/JdbcCoordinatorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/engine/jdbc/internal/JdbcCoordinatorTest.java @@ -62,8 +62,6 @@ public class JdbcCoordinatorTest { when( sessionContext.getObserver() ).thenReturn( jdbcObserver ); JdbcServices jdbcServices = Mockito.mock( JdbcServices.class ); - when( serviceRegistry.getService( eq( JdbcServices.class ) ) ).thenReturn( - jdbcServices ); ConfigurationService configurationService = Mockito.mock( ConfigurationService.class ); when( serviceRegistry.getService( eq( ConfigurationService.class ) ) ).thenReturn( @@ -77,7 +75,8 @@ public class JdbcCoordinatorTest { JdbcCoordinatorImpl jdbcCoordinator = new JdbcCoordinatorImpl( null, - sessionOwner + sessionOwner, + jdbcServices ); Batch currentBatch = Mockito.mock( Batch.class ); diff --git a/hibernate-core/src/test/java/org/hibernate/event/SessionEventListenersManagerTest.java b/hibernate-core/src/test/java/org/hibernate/event/SessionEventListenersManagerTest.java new file mode 100644 index 0000000000..d4eae4acda --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/event/SessionEventListenersManagerTest.java @@ -0,0 +1,66 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.event; + +import org.hibernate.BaseSessionEventListener; +import org.hibernate.SessionEventListener; +import org.hibernate.engine.internal.SessionEventListenerManagerImpl; + +import org.hibernate.testing.junit4.BaseUnitTestCase; +import org.junit.Assert; +import org.junit.Test; + +public class SessionEventListenersManagerTest extends BaseUnitTestCase { + + @Test + public void testListenerAppending() { + StringBuilder sb = new StringBuilder(); + SessionEventListener a = new TestSessionEventListener( sb , 'a' ); + SessionEventListener b = new TestSessionEventListener( sb , 'b' ); + SessionEventListener c = new TestSessionEventListener( sb , 'c' ); + SessionEventListener d = new TestSessionEventListener( sb , 'd' ); + SessionEventListenerManagerImpl l = new SessionEventListenerManagerImpl( a, b ); + l.addListener( c, d ); + l.dirtyCalculationEnd( true ); + Assert.assertEquals( "abcd", sb.toString() ); + l.dirtyCalculationEnd( true ); + Assert.assertEquals( "abcdabcd", sb.toString() ); + l.addListener( new TestSessionEventListener( sb , 'e' ) ); + l.dirtyCalculationEnd( true ); + Assert.assertEquals( "abcdabcdabcde", sb.toString() ); + } + + @Test + public void testEmptyListenerAppending() { + StringBuilder sb = new StringBuilder(); + SessionEventListenerManagerImpl l = new SessionEventListenerManagerImpl(); + l.dirtyCalculationEnd( true ); + Assert.assertEquals( "", sb.toString() ); + l.addListener( new TestSessionEventListener( sb , 'e' ) ); + l.dirtyCalculationEnd( true ); + Assert.assertEquals( "e", sb.toString() ); + } + + private static class TestSessionEventListener extends BaseSessionEventListener { + + private final StringBuilder sb; + private final char theChar; + + public TestSessionEventListener(StringBuilder sb, char theChar) { + this.sb = sb; + this.theChar = theChar; + } + + //Just picking any method. This one is funny.. + @Override + public void dirtyCalculationEnd(boolean dirty) { + sb.append( theChar ); + } + + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/LockTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/LockTest.java index dc6e41c282..09c149d46c 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/LockTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/lock/LockTest.java @@ -559,7 +559,6 @@ public class LockTest extends BaseEntityManagerFunctionalTestCase { @SkipForDialect(HSQLDialect.class) // ASE15.5 will generate select...holdlock and fail at this test, but ASE15.7 passes it. Skip it for ASE15.5 // only. - @SkipForDialect(value = { SybaseASE15Dialect.class }, strictMatching = true, jiraKey = "HHH-6820") @SkipForDialect(value = { SQLServerDialect.class }) public void testContendedPessimisticLock() throws Exception { final CountDownLatch latch = new CountDownLatch( 1 ); diff --git a/hibernate-core/src/test/java/org/hibernate/sharedSession/SessionWithSharedConnectionTest.java b/hibernate-core/src/test/java/org/hibernate/sharedSession/SessionWithSharedConnectionTest.java index 703174f6f8..ebec824f32 100644 --- a/hibernate-core/src/test/java/org/hibernate/sharedSession/SessionWithSharedConnectionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/sharedSession/SessionWithSharedConnectionTest.java @@ -270,7 +270,9 @@ public class SessionWithSharedConnectionTest extends BaseCoreFunctionalTestCase } assertNotNull("Observers field was not found", field); - assertEquals(0, ((Collection) field.get(((SessionImplementor) session).getTransactionCoordinator())).size()); + //Some of these collections could be lazily initialize: check for null before invoking size() + final Collection collection = (Collection) field.get( ( (SessionImplementor) session ).getTransactionCoordinator() ); + assertTrue(collection == null || collection.size() == 0 ); //open secondary sessions with managed options and immediately close Session secondarySession; diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/EntityTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/EntityTest.java index afaf60ebd8..5dd684f870 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/EntityTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/EntityTest.java @@ -24,9 +24,7 @@ import org.hibernate.Transaction; import org.hibernate.boot.MetadataBuilder; import org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl; import org.hibernate.dialect.Dialect; -import org.hibernate.dialect.MySQL57Dialect; -import org.hibernate.dialect.MySQL8Dialect; -import org.hibernate.dialect.MySQLDialect; +import org.hibernate.dialect.MySQL5Dialect; import org.hibernate.dialect.Oracle10gDialect; import org.hibernate.query.Query; import org.hibernate.tool.hbm2ddl.SchemaExport; @@ -373,7 +371,7 @@ public class EntityTest extends BaseNonConfigCoreFunctionalTestCase { @SkipForDialect(value = Oracle10gDialect.class, comment = "oracle12c returns time in getDate. For now, skip.") public void testTemporalType() throws Exception { - final ZoneId zoneId = ( Dialect.getDialect() instanceof MySQL8Dialect ) ? ZoneId.of( "UTC") + final ZoneId zoneId = ( Dialect.getDialect() instanceof MySQL5Dialect ) ? ZoneId.of( "UTC") : ZoneId.systemDefault(); Flight airFrance = doInHibernate( this::sessionFactory, session -> { diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/EmbeddedTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/EmbeddedTest.java index 83cdea6f8e..526a7e062d 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/EmbeddedTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/EmbeddedTest.java @@ -18,8 +18,10 @@ import org.hibernate.Session; import org.hibernate.Transaction; import org.hibernate.boot.MetadataBuilder; import org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl; +import org.hibernate.dialect.SybaseDialect; import org.hibernate.testing.FailureExpected; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; import org.hibernate.testing.transaction.TransactionUtil; @@ -27,6 +29,7 @@ import org.hibernate.test.annotations.embedded.FloatLeg.RateIndex; import org.hibernate.test.annotations.embedded.Leg.Frequency; import org.hibernate.test.util.SchemaUtil; +import org.junit.After; import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -39,6 +42,16 @@ import static org.junit.Assert.assertTrue; * @author Emmanuel Bernard */ public class EmbeddedTest extends BaseNonConfigCoreFunctionalTestCase { + + @After + public void cleanup() { + TransactionUtil.doInHibernate( this::sessionFactory, session ->{ + for ( Person person : session.createQuery( "from Person", Person.class ).getResultList() ) { + session.delete( person ); + } + } ); + } + @Test public void testSimple() throws Exception { Person person = new Person(); @@ -145,6 +158,7 @@ public class EmbeddedTest extends BaseNonConfigCoreFunctionalTestCase { @Test @TestForIssue(jiraKey = "HHH-8172") + @SkipForDialect( value = SybaseDialect.class, comment = "skip for Sybase because (null = null) evaluates to true") @FailureExpected(jiraKey = "HHH-8172") public void testQueryWithEmbeddedParameterOneNull() throws Exception { Person person = new Person(); @@ -601,7 +615,7 @@ public class EmbeddedTest extends BaseNonConfigCoreFunctionalTestCase { public void testDefaultCollectionTable() throws Exception { //are the tables correct? assertTrue( SchemaUtil.isTablePresent( "WealthyPerson_vacationHomes", metadata() ) ); - assertTrue( SchemaUtil.isTablePresent( "WealthyPerson_legacyVacationHomes", metadata() ) ); + assertTrue( SchemaUtil.isTablePresent( "WelPers_LegacyVacHomes", metadata() ) ); assertTrue( SchemaUtil.isTablePresent( "WelPers_VacHomes", metadata() ) ); //just to make sure, use the mapping diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/WealthyPerson.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/WealthyPerson.java index 3b24b37908..05df8f1df6 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/WealthyPerson.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/embedded/WealthyPerson.java @@ -19,6 +19,7 @@ public class WealthyPerson extends Person { protected Set
vacationHomes = new HashSet
(); @ElementCollection + @CollectionTable(name = "WelPers_LegacyVacHomes") protected Set
legacyVacationHomes = new HashSet
(); @ElementCollection diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/OneToManyTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/OneToManyTest.java index df06757ac2..0027981c70 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/OneToManyTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetomany/OneToManyTest.java @@ -37,6 +37,8 @@ import org.hibernate.mapping.Column; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Table; +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; import org.hibernate.test.annotations.Customer; @@ -359,6 +361,7 @@ public class OneToManyTest extends BaseNonConfigCoreFunctionalTestCase { } @Test + @RequiresDialectFeature(DialectChecks.SupportsCascadeDeleteCheck.class) public void testCascadeDeleteWithUnidirectionalAssociation() throws Exception { OnDeleteUnidirectionalOneToManyChild child = new OnDeleteUnidirectionalOneToManyChild(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/query/QueryAndSQLTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/query/QueryAndSQLTest.java index eea98617b9..59089a6a31 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/query/QueryAndSQLTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/query/QueryAndSQLTest.java @@ -103,7 +103,7 @@ public class QueryAndSQLTest extends BaseCoreFunctionalTestCase { sessionFactory() ); String sql = String.format( - "select TABLE_NAME , %s from all_tables where TABLE_NAME = 'AUDIT_ACTIONS' ", + "select TABLE_NAME , %s from ALL_TABLES where TABLE_NAME = 'AUDIT_ACTIONS' ", dateFunctionRendered ); Session s = openSession(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/NaturalIdInUninitializedAssociationTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/NaturalIdInUninitializedAssociationTest.java new file mode 100644 index 0000000000..1f86b4aa83 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/NaturalIdInUninitializedAssociationTest.java @@ -0,0 +1,173 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.LazyToOne; +import org.hibernate.annotations.LazyToOneOption; +import org.hibernate.annotations.NaturalId; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancementOptions; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +/** + * @author Gail Badner + */ +@SuppressWarnings({"unused", "WeakerAccess","ResultOfMethodCallIgnored"}) +@TestForIssue( jiraKey = "HHH-13607" ) +@RunWith( BytecodeEnhancerRunner.class ) +@EnhancementOptions( lazyLoading = true ) +public class NaturalIdInUninitializedAssociationTest extends BaseNonConfigCoreFunctionalTestCase { + + @Test + public void testImmutableNaturalId() { + inTransaction( + session -> { + final AnEntity e = session.get( AnEntity.class, 3 ); + assertFalse( Hibernate.isPropertyInitialized( e,"entityImmutableNaturalId" ) ); + } + ); + + inTransaction( + session -> { + final AnEntity e = session.get( AnEntity.class, 3 ); + assertEquals( "immutable name", e.entityImmutableNaturalId.name ); + } + ); + } + + @Test + public void testMutableNaturalId() { + inTransaction( + session -> { + final AnEntity e = session.get( AnEntity.class, 3 ); + assertFalse( Hibernate.isPropertyInitialized( e,"entityMutableNaturalId" ) ); + } + ); + + inTransaction( + session -> { + final AnEntity e = session.get( AnEntity.class, 3 ); + assertEquals( "mutable name", e.entityMutableNaturalId.name ); + } + ); + } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.FORMAT_SQL, "false" ); + } + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + super.configureSessionFactoryBuilder( sfb ); + sfb.applyStatisticsSupport( true ); + sfb.applySecondLevelCacheSupport( false ); + sfb.applyQueryCacheSupport( false ); + } + + @Override + protected void applyMetadataSources(MetadataSources sources) { + super.applyMetadataSources( sources ); + sources.addAnnotatedClass( AnEntity.class ); + sources.addAnnotatedClass( EntityMutableNaturalId.class ); + sources.addAnnotatedClass( EntityImmutableNaturalId.class ); + } + + @Before + public void prepareTestData() { + inTransaction( + session -> { + EntityMutableNaturalId entityMutableNaturalId = new EntityMutableNaturalId( 1, "mutable name" ); + EntityImmutableNaturalId entityImmutableNaturalId = new EntityImmutableNaturalId( 2, "immutable name" ); + AnEntity anEntity = new AnEntity(); + anEntity.id = 3; + anEntity.entityImmutableNaturalId = entityImmutableNaturalId; + anEntity.entityMutableNaturalId = entityMutableNaturalId; + session.persist( anEntity ); + } + ); + } + + @After + public void cleanUpTestData() { + inTransaction( + session -> { + session.delete( session.get( AnEntity.class, 3 ) ); + } + ); + } + + @Entity(name = "AnEntity") + public static class AnEntity { + @Id + private int id; + + @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) + @LazyToOne(LazyToOneOption.NO_PROXY ) + private EntityMutableNaturalId entityMutableNaturalId; + + @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) + @LazyToOne(LazyToOneOption.NO_PROXY ) + private EntityImmutableNaturalId entityImmutableNaturalId; + } + + @Entity(name = "EntityMutableNaturalId") + public static class EntityMutableNaturalId { + @Id + private int id; + + @NaturalId(mutable = true) + private String name; + + public EntityMutableNaturalId() { + } + + public EntityMutableNaturalId(int id, String name) { + this.id = id; + this.name = name; + } + } + + @Entity(name = "EntityImmutableNaturalId") + public static class EntityImmutableNaturalId { + @Id + private int id; + + @NaturalId(mutable = false) + private String name; + + public EntityImmutableNaturalId() { + } + + public EntityImmutableNaturalId(int id, String name) { + this.id = id; + this.name = name; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/StatelessQueryScrollingTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/StatelessQueryScrollingTest.java index 1bc6da1809..2aa0d1cdf4 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/StatelessQueryScrollingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/StatelessQueryScrollingTest.java @@ -24,6 +24,7 @@ import org.hibernate.StatelessSession; import org.hibernate.boot.MetadataSources; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.DB2Dialect; import org.hibernate.query.Query; import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; @@ -73,7 +74,17 @@ public class StatelessQueryScrollingTest extends BaseNonConfigCoreFunctionalTest try { final Query query = statelessSession.createQuery( "select p from Producer p join fetch p.products" ); - scrollableResults = query.scroll( ScrollMode.FORWARD_ONLY ); + if ( getDialect() instanceof DB2Dialect ) { + /* + FetchingScrollableResultsImp#next() in order to check if the ResultSet is empty calls ResultSet#isBeforeFirst() + but the support for ResultSet#isBeforeFirst() is optional for ResultSets with a result + set type of TYPE_FORWARD_ONLY and db2 does not support it. + */ + scrollableResults = query.scroll( ScrollMode.SCROLL_INSENSITIVE ); + } + else { + scrollableResults = query.scroll( ScrollMode.FORWARD_ONLY ); + } while ( scrollableResults.next() ) { Producer producer = (Producer) scrollableResults.get( 0 ); assertTrue( Hibernate.isInitialized( producer ) ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/NaturalIdInUninitializedProxyTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/NaturalIdInUninitializedProxyTest.java new file mode 100644 index 0000000000..37f677804e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/NaturalIdInUninitializedProxyTest.java @@ -0,0 +1,150 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.bytecode.enhancement.lazy.proxy; + +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.NaturalId; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.bytecode.enhancement.EnhancementOptions; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +/** + * @author Gail Badner + */ +@SuppressWarnings({"unused", "WeakerAccess","ResultOfMethodCallIgnored"}) +@TestForIssue( jiraKey = "HHH-13607" ) +@RunWith( BytecodeEnhancerRunner.class ) +@EnhancementOptions( lazyLoading = true ) +public class NaturalIdInUninitializedProxyTest extends BaseNonConfigCoreFunctionalTestCase { + + @Test + public void testImmutableNaturalId() { + inTransaction( + session -> { + final EntityImmutableNaturalId e = session.getReference( EntityImmutableNaturalId.class, 1 ); + assertFalse( Hibernate.isInitialized( e ) ); + } + ); + + inTransaction( + session -> { + final EntityImmutableNaturalId e = session.get( EntityImmutableNaturalId.class, 1 ); + assertEquals( "name", e.name ); + } + ); + } + + @Test + public void testMutableNaturalId() { + inTransaction( + session -> { + final EntityMutableNaturalId e = session.getReference( EntityMutableNaturalId.class, 1 ); + assertFalse( Hibernate.isInitialized( e ) ); + } + ); + + inTransaction( + session -> { + final EntityMutableNaturalId e = session.get( EntityMutableNaturalId.class, 1 ); + assertEquals( "name", e.name ); + } + ); + } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" ); + ssrb.applySetting( AvailableSettings.FORMAT_SQL, "false" ); + } + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + super.configureSessionFactoryBuilder( sfb ); + sfb.applyStatisticsSupport( true ); + sfb.applySecondLevelCacheSupport( false ); + sfb.applyQueryCacheSupport( false ); + } + + @Override + protected void applyMetadataSources(MetadataSources sources) { + super.applyMetadataSources( sources ); + sources.addAnnotatedClass( EntityMutableNaturalId.class ); + sources.addAnnotatedClass( EntityImmutableNaturalId.class ); + } + + @Before + public void prepareTestData() { + inTransaction( + session -> { + session.persist( new EntityMutableNaturalId( 1, "name" ) ); + session.persist( new EntityImmutableNaturalId( 1, "name" ) ); + } + ); + } + + @After + public void cleanUpTestData() { + inTransaction( + session -> { + session.createQuery( "delete from EntityMutableNaturalId" ).executeUpdate(); + session.createQuery( "delete from EntityImmutableNaturalId" ).executeUpdate(); + } + ); + } + + @Entity(name = "EntityMutableNaturalId") + public static class EntityMutableNaturalId { + @Id + private int id; + + @NaturalId(mutable = true) + private String name; + + public EntityMutableNaturalId() { + } + + public EntityMutableNaturalId(int id, String name) { + this.id = id; + this.name = name; + } + } + + @Entity(name = "EntityImmutableNaturalId") + public static class EntityImmutableNaturalId { + @Id + private int id; + + @NaturalId(mutable = false) + private String name; + + public EntityImmutableNaturalId() { + } + + public EntityImmutableNaturalId(int id, String name) { + this.id = id; + this.name = name; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/cache/SharedDomainDataAndQueryResultsTest.java b/hibernate-core/src/test/java/org/hibernate/test/cache/SharedDomainDataAndQueryResultsTest.java new file mode 100644 index 0000000000..5de7a14cb6 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/cache/SharedDomainDataAndQueryResultsTest.java @@ -0,0 +1,435 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.cache; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.NamedQuery; +import javax.persistence.QueryHint; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cache.spi.CacheImplementor; +import org.hibernate.cache.spi.DomainDataRegion; +import org.hibernate.cache.spi.QueryResultsCache; +import org.hibernate.cache.spi.Region; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.stat.CacheRegionStatistics; +import org.hibernate.stat.QueryStatistics; +import org.hibernate.stat.SecondLevelCacheStatistics; +import org.hibernate.stat.Statistics; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.cache.CachingRegionFactory; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +/** + * @author Gail Badner + */ +public class SharedDomainDataAndQueryResultsTest extends BaseNonConfigCoreFunctionalTestCase { + + private static final String QUERY = "SELECT a FROM Dog a"; + private static final String REGION = "TheRegion"; + private static final String PREFIX = "test"; + + @Test + @TestForIssue( jiraKey = "HHH-13586") + public void testAllCachedStatistics() { + + final Statistics statistics = sessionFactory().getStatistics(); + statistics.clear(); + + final SecondLevelCacheStatistics regionStatisticsDeprecated = statistics.getSecondLevelCacheStatistics( + PREFIX + '.' + REGION + ); + final CacheRegionStatistics regionStatistics = statistics.getCacheRegionStatistics( REGION ); + assertSame( regionStatistics, regionStatisticsDeprecated ); + + final QueryStatistics queryStatistics = statistics.getQueryStatistics( QUERY ); + + doInHibernate( + this::sessionFactory, session -> { + + Dog yogi = session.get( Dog.class, "Yogi" ); + + assertEquals( 1, statistics.getSecondLevelCacheHitCount() ); + assertEquals( 0, statistics.getSecondLevelCachePutCount() ); + assertEquals( 0, statistics.getSecondLevelCacheMissCount() ); + + assertEquals( 1, regionStatistics.getHitCount() ); + assertEquals( 0, regionStatistics.getPutCount() ); + assertEquals( 0, regionStatistics.getMissCount() ); + + assertEquals( 0, statistics.getQueryCacheHitCount() ); + assertEquals( 0, statistics.getQueryCachePutCount() ); + assertEquals( 0, statistics.getQueryCacheMissCount() ); + + assertEquals( 0, queryStatistics.getCacheHitCount() ); + assertEquals( 0, queryStatistics.getCachePutCount() ); + assertEquals( 0, queryStatistics.getCacheMissCount() ); + + assertFalse( Hibernate.isInitialized( yogi.nickNames ) ); + Hibernate.initialize( yogi.nickNames ); + + assertEquals( 1, statistics.getSecondLevelCacheHitCount() ); + assertEquals( 1, statistics.getSecondLevelCachePutCount() ); + assertEquals( 1, statistics.getSecondLevelCacheMissCount() ); + + assertEquals( 1, regionStatistics.getHitCount() ); + assertEquals( 1, regionStatistics.getPutCount() ); + assertEquals( 1, regionStatistics.getMissCount() ); + + assertEquals( 0, statistics.getQueryCacheHitCount() ); + assertEquals( 0, statistics.getQueryCachePutCount() ); + assertEquals( 0, statistics.getQueryCacheMissCount() ); + + assertEquals( 0, queryStatistics.getCacheHitCount() ); + assertEquals( 0, queryStatistics.getCachePutCount() ); + assertEquals( 0, queryStatistics.getCacheMissCount() ); + } + ); + + doInHibernate( + this::sessionFactory, session -> { + + Dog yogi = session.get( Dog.class, "Yogi" ); + + assertEquals( 2, statistics.getSecondLevelCacheHitCount() ); + assertEquals( 1, statistics.getSecondLevelCachePutCount() ); + assertEquals( 1, statistics.getSecondLevelCacheMissCount() ); + + assertEquals( 2, regionStatistics.getHitCount() ); + assertEquals( 1, regionStatistics.getPutCount() ); + assertEquals( 1, regionStatistics.getMissCount() ); + + assertEquals( 0, statistics.getQueryCacheHitCount() ); + assertEquals( 0, statistics.getQueryCachePutCount() ); + assertEquals( 0, statistics.getQueryCacheMissCount() ); + + assertEquals( 0, queryStatistics.getCacheHitCount() ); + assertEquals( 0, queryStatistics.getCachePutCount() ); + assertEquals( 0, queryStatistics.getCacheMissCount() ); + + assertFalse( Hibernate.isInitialized( yogi.nickNames ) ); + Hibernate.initialize( yogi.nickNames ); + + assertEquals( 3, statistics.getSecondLevelCacheHitCount() ); + assertEquals( 1, statistics.getSecondLevelCachePutCount() ); + assertEquals( 1, statistics.getSecondLevelCacheMissCount() ); + + assertEquals( 3, regionStatistics.getHitCount() ); + assertEquals( 1, regionStatistics.getPutCount() ); + assertEquals( 1, regionStatistics.getMissCount() ); + + assertEquals( 0, statistics.getQueryCacheHitCount() ); + assertEquals( 0, statistics.getQueryCachePutCount() ); + assertEquals( 0, statistics.getQueryCacheMissCount() ); + + assertEquals( 0, queryStatistics.getCacheHitCount() ); + assertEquals( 0, queryStatistics.getCachePutCount() ); + assertEquals( 0, queryStatistics.getCacheMissCount() ); + + } + ); + + doInHibernate( + this::sessionFactory, session -> { + + List dogs = session.createNamedQuery( "Dog.findAll", Dog.class ).list(); + + assertEquals( 2, dogs.size() ); + + // statistics.getSecondLevelCacheHitCount() only includes entity/collection hits + assertEquals( 3, statistics.getSecondLevelCacheHitCount() ); + // statistics.getSecondLevelCachePutCount() only includes entity/collection puts + assertEquals( 3, statistics.getSecondLevelCachePutCount() ); + // statistics.getSecondLevelCacheMissCount() only includes entity/collection misses + assertEquals( 1, statistics.getSecondLevelCacheMissCount() ); + + // regionStatistics has hits/puts/misses for entities/collections/query results + // Query results missed and put in cache. + // Reference caching is not being used, so entities are not looked up in the cache. + // Entities get put in cache. + assertEquals( 3, regionStatistics.getHitCount() ); + // 2 Dog puts; 1 query put + assertEquals( 4, regionStatistics.getPutCount() ); + // 1 query miss + assertEquals( 2, regionStatistics.getMissCount() ); + + assertEquals( 0, statistics.getQueryCacheHitCount() ); + assertEquals( 1, statistics.getQueryCachePutCount() ); + assertEquals( 1, statistics.getQueryCacheMissCount() ); + + assertEquals( 0, queryStatistics.getCacheHitCount() ); + assertEquals( 1, queryStatistics.getCachePutCount() ); + assertEquals( 1, queryStatistics.getCacheMissCount() ); + + for ( Dog dog : dogs ) { + assertFalse( Hibernate.isInitialized( dog.nickNames ) ); + Hibernate.initialize( dog.nickNames ); + } + + // yogi.nickNames will be found in the cache as a cache hit. + // The other collection will be a cache miss and will be put in the cache + // statistics.getSecondLevelCacheHitCount() only includes entity/collection hits + assertEquals( 4, statistics.getSecondLevelCacheHitCount() ); + // statistics.getSecondLevelCachePutCount() only includes entity/collection puts + assertEquals( 4, statistics.getSecondLevelCachePutCount() ); + // statistics.getSecondLevelCacheMissCount() only includes entity/collection misses + assertEquals( 2, statistics.getSecondLevelCacheMissCount() ); + + // regionStatistics includes hits/puts/misses for entities/collections/query results + assertEquals( 4, regionStatistics.getHitCount() ); + // 2 Dog puts; 1 query put + assertEquals( 5, regionStatistics.getPutCount() ); + // 1 query miss + assertEquals( 3, regionStatistics.getMissCount() ); + + assertEquals( 0, statistics.getQueryCacheHitCount() ); + assertEquals( 1, statistics.getQueryCachePutCount() ); + assertEquals( 1, statistics.getQueryCacheMissCount() ); + + assertEquals( 0, queryStatistics.getCacheHitCount() ); + assertEquals( 1, queryStatistics.getCachePutCount() ); + assertEquals( 1, queryStatistics.getCacheMissCount() ); + + } + ); + + doInHibernate( + this::sessionFactory, session -> { + + List dogs = session.getNamedQuery( "Dog.findAll" ).list(); + + assertEquals( 2, dogs.size() ); + + // statistics.getSecondLevelCacheHitCount() only includes entity/collection hits + assertEquals( 6, statistics.getSecondLevelCacheHitCount() ); + // statistics.getSecondLevelCachePutCount() only includes entity/collection puts + assertEquals( 4, statistics.getSecondLevelCachePutCount() ); + // statistics.getSecondLevelCacheMissCount() only includes entity/collection misses + assertEquals( 2, statistics.getSecondLevelCacheMissCount() ); + + // regionStatistics includes hits/puts/misses for entities/collections/query results + // Query results will be found in the cache. + // The 2 Dog entities will be found in the cache. + assertEquals( 7, regionStatistics.getHitCount() ); + assertEquals( 5, regionStatistics.getPutCount() ); + assertEquals( 3, regionStatistics.getMissCount() ); + + assertEquals( 1, statistics.getQueryCacheHitCount() ); + assertEquals( 1, statistics.getQueryCachePutCount() ); + assertEquals( 1, statistics.getQueryCacheMissCount() ); + + assertEquals( 1, queryStatistics.getCacheHitCount() ); + assertEquals( 1, queryStatistics.getCachePutCount() ); + assertEquals( 1, queryStatistics.getCacheMissCount() ); + + for ( Dog dog : dogs ) { + assertFalse( Hibernate.isInitialized( dog.nickNames ) ); + Hibernate.initialize( dog.nickNames ); + } + + // Both Dog.nickNames will be found in the cache as a cache hit. + + // statistics.getSecondLevelCacheHitCount() only includes entity/collection hits + assertEquals( 8, statistics.getSecondLevelCacheHitCount() ); + // statistics.getSecondLevelCachePutCount() only includes entity/collection puts + assertEquals( 4, statistics.getSecondLevelCachePutCount() ); + // statistics.getSecondLevelCacheMissCount() only includes entity/collection misses + assertEquals( 2, statistics.getSecondLevelCacheMissCount() ); + + // regionStatistics includes hits/puts/misses for entities/collections/query results + assertEquals( 9, regionStatistics.getHitCount() ); + // 2 Dog puts; 1 query put + assertEquals( 5, regionStatistics.getPutCount() ); + // 1 query miss + assertEquals( 3, regionStatistics.getMissCount() ); + + assertEquals( 1, statistics.getQueryCacheHitCount() ); + assertEquals( 1, statistics.getQueryCachePutCount() ); + assertEquals( 1, statistics.getQueryCacheMissCount() ); + + assertEquals( 1, queryStatistics.getCacheHitCount() ); + assertEquals( 1, queryStatistics.getCachePutCount() ); + assertEquals( 1, queryStatistics.getCacheMissCount() ); + } + ); + } + + @Test + @TestForIssue( jiraKey = "HHH-13586") + public void testCacheImplementorGetRegion() { + rebuildSessionFactory(); + + final CacheImplementor cache = sessionFactory().getCache(); + final Region domainDataRegion = cache.getRegion( REGION ); + assertTrue( DomainDataRegion.class.isInstance( domainDataRegion ) ); + assertEquals( REGION, domainDataRegion.getName() ); + + // There should not be a QueryResultsRegion named REGION until + // the named query is executed. + assertNull( cache.getQueryResultsCacheStrictly( REGION ) ); + + doInHibernate( + this::sessionFactory, session -> { + session.createNamedQuery( "Dog.findAll", Dog.class ).list(); + } + ); + + // No there should be a QueryResultsCache named REGION + final QueryResultsCache queryResultsCache = cache.getQueryResultsCacheStrictly( REGION ); + assertNotNull( queryResultsCache ); + assertEquals( REGION, queryResultsCache.getRegion().getName() ); + + // Now there is a DomainDataRegion and QueryResultsRegion named REGION. + // Make sure that the same DomainDataRegion is returned by cache.getRegion( REGION ). + assertSame( domainDataRegion, cache.getRegion( REGION ) ); + } + + @Test + @TestForIssue( jiraKey = "HHH-13586") + public void testEvictCaches() { + + final Statistics statistics = sessionFactory().getStatistics(); + statistics.clear(); + + assertEquals( 0, statistics.getSecondLevelCacheHitCount() ); + assertEquals( 0, statistics.getSecondLevelCachePutCount() ); + assertEquals( 0, statistics.getSecondLevelCacheMissCount() ); + + assertEquals( 0, statistics.getQueryCacheHitCount() ); + assertEquals( 0, statistics.getQueryCachePutCount() ); + assertEquals( 0, statistics.getQueryCacheMissCount() ); + + doInHibernate( + this::sessionFactory, session -> { + + Dog yogi = session.get( Dog.class, "Yogi" ); + assertEquals( 1, statistics.getSecondLevelCacheHitCount() ); + assertEquals( 0, statistics.getSecondLevelCachePutCount() ); + assertEquals( 0, statistics.getSecondLevelCacheMissCount() ); + // put the collection in the cache + Hibernate.initialize( yogi.nickNames ); + assertEquals( 1, statistics.getSecondLevelCacheHitCount() ); + assertEquals( 1, statistics.getSecondLevelCachePutCount() ); + assertEquals( 1, statistics.getSecondLevelCacheMissCount() ); + + session.createNamedQuery( "Dog.findAll", Dog.class ).list(); + + assertEquals( 0, statistics.getQueryCacheHitCount() ); + assertEquals( 1, statistics.getQueryCachePutCount() ); + assertEquals( 1, statistics.getQueryCacheMissCount() ); + + session.clear(); + + session.createNamedQuery( "Dog.findAll", Dog.class ).list(); + assertEquals( 1, statistics.getQueryCacheHitCount() ); + assertEquals( 1, statistics.getQueryCachePutCount() ); + assertEquals( 1, statistics.getQueryCacheMissCount() ); + + session.clear(); + statistics.clear(); + + sessionFactory().getCache().evictRegion( REGION ); + + session.createNamedQuery( "Dog.findAll", Dog.class ).list(); + + assertEquals( 0, statistics.getSecondLevelCacheHitCount() ); + assertEquals( 0, statistics.getQueryCacheHitCount() ); + } + ); + + } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.USE_SECOND_LEVEL_CACHE, true ); + ssrb.applySetting( AvailableSettings.USE_QUERY_CACHE, true ); + ssrb.applySetting( AvailableSettings.CACHE_REGION_PREFIX, PREFIX ); + ssrb.applySetting( AvailableSettings.CACHE_REGION_FACTORY, new CachingRegionFactory() ); + ssrb.applySetting( AvailableSettings.GENERATE_STATISTICS, "true" ); + } + + @Override + protected void applyMetadataSources(MetadataSources metadataSources) { + super.applyMetadataSources( metadataSources ); + metadataSources.addAnnotatedClass( Dog.class ); + } + + @Before + public void setupData() { + doInHibernate( + this::sessionFactory, session -> { + Dog yogi = new Dog( "Yogi" ); + yogi.nickNames.add( "The Yog" ); + yogi.nickNames.add( "Little Boy" ); + yogi.nickNames.add( "Yogaroni Macaroni" ); + Dog irma = new Dog( "Irma" ); + irma.nickNames.add( "Squirmy" ); + irma.nickNames.add( "Bird" ); + session.persist( yogi ); + session.persist( irma ); + } + ); + } + + @After + public void cleanupData() { + doInHibernate( + this::sessionFactory, session -> { + List dogs = session.createQuery( "from Dog", Dog.class ).getResultList(); + for ( Dog dog : dogs ) { + session.delete( dog ); + } + } + ); + } + + @Entity(name = "Dog") + @NamedQuery(name = "Dog.findAll", query = QUERY, + hints = { + @QueryHint(name = "org.hibernate.cacheable", value = "true"), + @QueryHint(name = "org.hibernate.cacheRegion", value = REGION) + } + ) + @Cache(usage = CacheConcurrencyStrategy.READ_ONLY, region=REGION) + public static class Dog { + @Id + private String name; + + @Cache(usage = CacheConcurrencyStrategy.READ_ONLY, region=REGION) + @ElementCollection + private Set nickNames = new HashSet<>(); + + public Dog(String name) { + this.name = name; + } + + public Dog() { + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/cascade/CascadeMergeToProxyEntityCopyAllowedTest.java b/hibernate-core/src/test/java/org/hibernate/test/cascade/CascadeMergeToProxyEntityCopyAllowedTest.java new file mode 100644 index 0000000000..0493ca0bca --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/cascade/CascadeMergeToProxyEntityCopyAllowedTest.java @@ -0,0 +1,315 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.cascade; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Random; +import java.util.Set; +import java.util.UUID; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.ManyToMany; +import javax.persistence.ManyToOne; +import javax.persistence.MappedSuperclass; +import javax.persistence.OneToMany; +import javax.persistence.Transient; +import javax.persistence.TypedQuery; +import javax.persistence.Version; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +@TestForIssue( jiraKey = "HHH-13590") +public class CascadeMergeToProxyEntityCopyAllowedTest extends BaseCoreFunctionalTestCase { + private Project defaultProject; + + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + AbstractEntity.class, + Event.class, + Project.class, + Speaker.class + }; + } + + @Test + public void test() { + final Event root = (Event) persistEntity( new Event( null, defaultProject ) ); + + Event rootFromDB = doInHibernate( + this::sessionFactory, session -> { + TypedQuery eventTypedQuery = session.createQuery( + "SELECT e FROM Event e LEFT JOIN FETCH e.speakers LEFT JOIN FETCH e.children LEFT JOIN FETCH e.project WHERE e.objectID = :oid", + Event.class + ); + + eventTypedQuery.setParameter( "oid", root.getObjectID() ); + + return eventTypedQuery.getSingleResult(); + + } + ); + assertNotNull( rootFromDB ); + assertEquals(0, rootFromDB.getChildren().size()); + assertEquals( 0, rootFromDB.getSpeakers().size() ); + assertEquals( root, rootFromDB ); + + Speaker speaker = (Speaker) persistEntity( new Speaker(defaultProject) ); + final long speakerId = speaker.getObjectID(); + + speaker = doInHibernate( + this::sessionFactory, session -> { + return session.find( Speaker.class, speakerId ); + } + ); + assertNotNull( speaker ); + + Event child = new Event(rootFromDB, defaultProject); + child.addSpeaker( speaker ); + + rootFromDB = (Event) persistEntity( rootFromDB ); + final long rootFromDBId = rootFromDB.getObjectID(); + rootFromDB = doInHibernate( + this::sessionFactory, session -> { + TypedQuery eventTypedQuery = session.createQuery( + "SELECT e FROM Event e LEFT JOIN FETCH e.speakers LEFT JOIN FETCH e.children LEFT JOIN FETCH e.project WHERE e.objectID = :oid", + Event.class + ); + + eventTypedQuery.setParameter( "oid", rootFromDBId ); + + return eventTypedQuery.getSingleResult(); + + } + ); + assertNotNull( rootFromDB ); + assertEquals(1, rootFromDB.getChildren().size()); + assertEquals(0, rootFromDB.getSpeakers().size()); + + } + + private Object persistEntity(Object entity ) { + return doInHibernate( + this::sessionFactory, session -> { + Object mergedEntity = session.merge( entity ); + session.persist( mergedEntity ); + session.flush(); + return mergedEntity; + } + ); + } + + @Override + protected void configure(Configuration cfg) { + super.configure( cfg ); + cfg.setProperty( AvailableSettings.MERGE_ENTITY_COPY_OBSERVER, "allow" ); + } + + @Before + public void setupData() { + Long objectId = doInHibernate( + this::sessionFactory, session -> { + Project project = (Project) session.merge( new Project() ); + session.persist( project ); + session.flush(); + return project.getObjectID(); + } + ); + doInHibernate( + this::sessionFactory, session -> { + TypedQuery projectTypedQuery = session.createQuery("SELECT p FROM Project p WHERE p.objectID = :oid", Project.class); + + projectTypedQuery.setParameter("oid", objectId); + + defaultProject = projectTypedQuery.getSingleResult(); + } + ); + } + + @MappedSuperclass + public static class AbstractEntity { + + static long INVALID_OBJECT_ID = -1 ; + + @Transient + protected static final Random RANDOM_GENERATOR = new Random(); + + @Id + @GeneratedValue + @Column(name = "id") + protected Long objectID = INVALID_OBJECT_ID; + + @Version + private int version; + + @Column(nullable = false, unique = true, length = 36) + private final String bID; + + protected AbstractEntity() { + bID = UUID.nameUUIDFromBytes( + ( Long.toString( System.currentTimeMillis() ) + RANDOM_GENERATOR.nextInt() ) + .getBytes() + ).toString(); + } + + public int getVersion() { + return version; + } + + public long getObjectID() { + return objectID; + } + + public static boolean isValidObjectID(long id) { + return (id > 0 && id != AbstractEntity.INVALID_OBJECT_ID); + } + + public boolean isPersistent() { + return isValidObjectID(getObjectID()); + } + + @Override + public String toString() { + return String.format("%s[id=%d]", getClass().getSimpleName(), getObjectID()); + } + + public String getBID() { + return bID; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + AbstractEntity that = (AbstractEntity) o; + + return bID != null ? bID.equals(that.bID) : that.bID == null; + + } + + @Override + public int hashCode() { + return bID != null ? bID.hashCode() : 0; + } + } + + @Entity(name = "Event") + @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) + public static class Event extends AbstractEntity { + + @ManyToOne(targetEntity = Event.class, fetch = FetchType.EAGER) + private Event parent; + + @OneToMany(targetEntity = Event.class, cascade = { CascadeType.ALL}, orphanRemoval = true, fetch = FetchType.LAZY, mappedBy = "parent") + private Set children = new HashSet<>(); + + @ManyToOne(targetEntity = Project.class, fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH}) + private Project project; + + @ManyToMany(targetEntity = Speaker.class, fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.REFRESH}) + private Set speakers = new HashSet<>(); + + + public Event() { + //framework purpose + } + + public Event(Event parent, Project project) { + setParent(parent); + setProject(project); + + if (parent == null) { + //nothing to do here, Event has no parent + } else { + parent.addChild(this); + } + } + + public void setParent(Event parent) { + this.parent = parent; + } + + public void setProject(Project project) { + this.project = project; + } + + public Event getParent() { + return parent; + } + + public Project getProject() { + return project; + } + + public Set getSpeakers() { + return Collections.unmodifiableSet( speakers ); + } + + public Set getChildren() { + return Collections.unmodifiableSet( children ); + } + + public void addSpeaker(Speaker speaker) { + assert speaker != null; + this.speakers.add(speaker); + } + + public void addChild(Event event) { + assert event != null; + this.children.add(event); + event.setParent(this); + } + + } + + @Entity(name = "Project") + @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) + public static class Project extends AbstractEntity { + + } + + @Entity(name = "Speaker") + @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) + public static class Speaker extends AbstractEntity { + + + @ManyToOne(targetEntity = Project.class, fetch = FetchType.LAZY, + cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH}) + private Project project; + + public Speaker() { + + } + + public Speaker(Project project) { + this.project = project; + } + + public Project getProject() { + return project; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/cascade/CascadeMergeToProxySimpleTest.java b/hibernate-core/src/test/java/org/hibernate/test/cascade/CascadeMergeToProxySimpleTest.java new file mode 100644 index 0000000000..972ad2aebc --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/cascade/CascadeMergeToProxySimpleTest.java @@ -0,0 +1,167 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.cascade; + +import java.util.HashSet; +import java.util.Random; +import java.util.Set; +import java.util.UUID; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.MappedSuperclass; +import javax.persistence.OneToMany; + +import org.hibernate.cfg.Configuration; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertNotNull; + +@TestForIssue( jiraKey = "HHH-13590") +public class CascadeMergeToProxySimpleTest extends BaseCoreFunctionalTestCase { + protected static final Random RANDOM_GENERATOR = new Random(); + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + AbstractEntity.class, + Event.class, + Project.class + }; + } + + @Test + public void test() { + final Event root = (Event) mergeEntity( new Event( generateBId(), new Project( generateBId() ) ) ); + Event rootFromDB = (Event) mergeEntity( root ); + + assertNotNull( rootFromDB ); + } + + private Object mergeEntity(Object entity) { + return doInHibernate( + this::sessionFactory, session -> { + return session.merge( entity ); + } + ); + } + + private String generateBId() { + return UUID.nameUUIDFromBytes( + ( Long.toString( System.currentTimeMillis() ) + RANDOM_GENERATOR.nextInt() ) + .getBytes() + ).toString(); + } + + @Override + protected void configure(Configuration cfg) { + super.configure( cfg ); + } + + @MappedSuperclass + public static class AbstractEntity { + + @Id + @GeneratedValue + @Column(name = "id") + protected Long objectID; + + @Column(nullable = false, unique = true, length = 36) + private String bID; + + protected AbstractEntity() { + } + + protected AbstractEntity(String bId) { + this.bID = bId; + } + public long getObjectID() { + return objectID; + } + + @Override + public String toString() { + return String.format("%s[id=%d]", getClass().getSimpleName(), getObjectID()); + } + + public String getBID() { + return bID; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if ( o == null || !this.getClass().isInstance( o ) ) return false; + + AbstractEntity that = (AbstractEntity) o; + + return bID != null ? bID.equals( that.bID) : that.bID == null; + } + + @Override + public int hashCode() { + return bID != null ? bID.hashCode() : 0; + } + } + + @Entity(name = "Event") + public static class Event extends AbstractEntity { + + @OneToMany(targetEntity = Event.class, cascade = { CascadeType.ALL}, orphanRemoval = true, fetch = FetchType.LAZY) + private Set children = new HashSet<>(); + + @ManyToOne(targetEntity = Project.class, fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH}) + private Project project; + + public Event() { + //framework purpose + } + + public Event(String bid, Project project) { + super( bid ); + setProject(project); + } + + public void setProject(Project project) { + this.project = project; + } + + public Project getProject() { + return project; + } + + public Set getChildren() { + return children; + } + + public void addChild(Event event) { + assert event != null; + this.children.add(event); + } + + } + + @Entity(name = "Project") + public static class Project extends AbstractEntity { + + Project() { + } + + Project(String bId) { + super( bId ); + } + + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/collection/multisession/MultipleSessionCollectionWarningTest.java b/hibernate-core/src/test/java/org/hibernate/test/collection/multisession/MultipleSessionCollectionWarningTest.java index 9cd6db69f8..e7a0090c74 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/collection/multisession/MultipleSessionCollectionWarningTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/collection/multisession/MultipleSessionCollectionWarningTest.java @@ -41,6 +41,7 @@ import org.hibernate.Hibernate; import org.hibernate.HibernateException; import org.hibernate.Session; import org.hibernate.collection.internal.AbstractPersistentCollection; +import org.hibernate.collection.internal.PersistentSet; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.spi.CollectionEntry; import org.hibernate.engine.spi.SessionImplementor; @@ -81,9 +82,8 @@ public class MultipleSessionCollectionWarningTest extends BaseCoreFunctionalTest // gets logged. s1 will not function properly so the transaction will ultimately need // to be rolled-back. - CollectionEntry ce = (CollectionEntry) ( (SessionImplementor) s1 ).getPersistenceContext() - .getCollectionEntries() - .remove( p.children ); + CollectionEntry ce = ( (SessionImplementor) s1 ).getPersistenceContext() + .removeCollectionEntry( (PersistentSet) p.children ); assertNotNull( ce ); // the collection session should still be s1; the collection is no longer "connected" because its @@ -132,9 +132,8 @@ public class MultipleSessionCollectionWarningTest extends BaseCoreFunctionalTest // gets logged. s1 will not function properly so the transaction will ultimately need // to be rolled-back. - CollectionEntry ce = (CollectionEntry) ( (SessionImplementor) s1 ).getPersistenceContext() - .getCollectionEntries() - .remove( p.children ); + CollectionEntry ce = ( (SessionImplementor) s1 ).getPersistenceContext() + .removeCollectionEntry( (PersistentSet) p.children ); assertNotNull( ce ); // the collection session should still be s1; the collection is no longer "connected" because its @@ -180,9 +179,8 @@ public class MultipleSessionCollectionWarningTest extends BaseCoreFunctionalTest // gets logged. s1 will not function properly so the transaction will ultimately need // to be rolled-back. - CollectionEntry ce = (CollectionEntry) ( (SessionImplementor) s1 ).getPersistenceContext() - .getCollectionEntries() - .remove( p.children ); + CollectionEntry ce = ( (SessionImplementor) s1 ).getPersistenceContext() + .removeCollectionEntry( (PersistentSet) p.children ); assertNotNull( ce ); // the collection session should still be s1; the collection is no longer "connected" because its diff --git a/hibernate-core/src/test/java/org/hibernate/test/events/AutoFlushEventListenerTest.java b/hibernate-core/src/test/java/org/hibernate/test/events/AutoFlushEventListenerTest.java new file mode 100644 index 0000000000..4999ffb2b8 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/events/AutoFlushEventListenerTest.java @@ -0,0 +1,143 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.events; + +import java.util.ArrayList; +import java.util.List; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import org.hibernate.FlushMode; +import org.hibernate.HibernateException; +import org.hibernate.Session; +import org.hibernate.boot.Metadata; +import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.event.service.spi.EventListenerRegistry; +import org.hibernate.event.spi.AutoFlushEvent; +import org.hibernate.event.spi.AutoFlushEventListener; +import org.hibernate.event.spi.EventType; +import org.hibernate.integrator.spi.Integrator; +import org.hibernate.service.spi.SessionFactoryServiceRegistry; + +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class AutoFlushEventListenerTest extends BaseCoreFunctionalTestCase { + + private TheListener listener = new TheListener(); + + @Test + public void testAutoFlushRequired() { + listener.events.clear(); + + Session s = openSession(); + s.beginTransaction(); + + s.persist( new Entity1() ); + assertEquals( 0, listener.events.size() ); + + // An entity of this type was persisted; a flush is required + session.createQuery( "select i from Entity1 i" ) + .setHibernateFlushMode( FlushMode.AUTO ) + .getResultList(); + assertEquals( 1, listener.events.size() ); + assertTrue( listener.events.get( 0 ).isFlushRequired() ); + + s.getTransaction().commit(); + assertEquals( 1, listener.events.size() ); + s.close(); + assertEquals( 1, listener.events.size() ); + } + + @Test + public void testAutoFlushNotRequired() { + listener.events.clear(); + + Session s = openSession(); + s.beginTransaction(); + + s.persist( new Entity2() ); + assertEquals( 0, listener.events.size() ); + + // No entity of this type was persisted; no flush is required + session.createQuery( "select i from Entity1 i" ) + .setHibernateFlushMode( FlushMode.AUTO ) + .getResultList(); + assertEquals( 1, listener.events.size() ); + assertFalse( listener.events.get( 0 ).isFlushRequired() ); + + s.getTransaction().commit(); + assertEquals( 1, listener.events.size() ); + s.close(); + assertEquals( 1, listener.events.size() ); + } + + @Override + protected void prepareBootstrapRegistryBuilder(BootstrapServiceRegistryBuilder builder) { + super.prepareBootstrapRegistryBuilder( builder ); + builder.applyIntegrator( + new Integrator() { + @Override + public void integrate( + Metadata metadata, + SessionFactoryImplementor sessionFactory, + SessionFactoryServiceRegistry serviceRegistry) { + serviceRegistry.getService( EventListenerRegistry.class ).appendListeners( + EventType.AUTO_FLUSH, + listener + ); + } + + @Override + public void disintegrate(SessionFactoryImplementor sessionFactory, + SessionFactoryServiceRegistry serviceRegistry) { + } + } + ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Entity1.class, Entity2.class }; + } + + @Entity(name = "Entity1") + static class Entity1 { + @Id + @GeneratedValue + private Integer id; + + public Entity1() { + } + } + + @Entity(name = "Entity2") + static class Entity2 { + @Id + @GeneratedValue + private Integer id; + + public Entity2() { + } + } + + private static class TheListener implements AutoFlushEventListener { + private List events = new ArrayList<>(); + + @Override + public void onAutoFlush(AutoFlushEvent event) throws HibernateException { + events.add( event ); + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/flush/TestAutoFlushBeforeQueryExecution.java b/hibernate-core/src/test/java/org/hibernate/test/flush/TestAutoFlushBeforeQueryExecution.java index 7d2ec3cd30..1a0631e80f 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/flush/TestAutoFlushBeforeQueryExecution.java +++ b/hibernate-core/src/test/java/org/hibernate/test/flush/TestAutoFlushBeforeQueryExecution.java @@ -60,7 +60,7 @@ public class TestAutoFlushBeforeQueryExecution extends BaseCoreFunctionalTestCas final PersistenceContext persistenceContext = ( (SessionImplementor) s ).getPersistenceContext(); final ActionQueue actionQueue = ( (SessionImpl) s ).getActionQueue(); - assertEquals( 1, persistenceContext.getCollectionEntries().size() ); + assertEquals( 1, persistenceContext.getCollectionEntriesSize() ); assertEquals( 1, persistenceContext.getCollectionsByKey().size() ); assertTrue( persistenceContext.getCollectionEntries().containsKey( publisher.getAuthors() ) ); assertTrue( persistenceContext.getCollectionsByKey().values().contains( publisher.getAuthors() ) ); @@ -73,7 +73,7 @@ public class TestAutoFlushBeforeQueryExecution extends BaseCoreFunctionalTestCas "autoflush collection update", s.createQuery( "select a from Publisher p join p.authors a" ).list().size() == 1 ); - assertEquals( 2, persistenceContext.getCollectionEntries().size() ); + assertEquals( 2, persistenceContext.getCollectionEntriesSize() ); assertEquals( 2, persistenceContext.getCollectionsByKey().size() ); assertTrue( persistenceContext.getCollectionEntries().containsKey( publisher.getAuthors() ) ); assertTrue( persistenceContext.getCollectionEntries().containsKey( author1.getBooks() ) ); @@ -88,7 +88,7 @@ public class TestAutoFlushBeforeQueryExecution extends BaseCoreFunctionalTestCas assertTrue( "autoflush collection update", s.createQuery( "select a from Publisher p join p.authors a" ).list().size() == 0 ); - assertEquals( 1, persistenceContext.getCollectionEntries().size() ); + assertEquals( 1, persistenceContext.getCollectionEntriesSize() ); assertEquals( 1, persistenceContext.getCollectionsByKey().size() ); assertTrue( persistenceContext.getCollectionEntries().containsKey( publisher.getAuthors() ) ); assertTrue( persistenceContext.getCollectionsByKey().values().contains( publisher.getAuthors() ) ); @@ -102,7 +102,7 @@ public class TestAutoFlushBeforeQueryExecution extends BaseCoreFunctionalTestCas publisher.getAuthors().add( author2 ); List results = s.createQuery( "select a from Publisher p join p.authors a" ).list(); assertEquals( 1, results.size() ); - assertEquals( 2, persistenceContext.getCollectionEntries().size() ); + assertEquals( 2, persistenceContext.getCollectionEntriesSize() ); assertEquals( 2, persistenceContext.getCollectionsByKey().size() ); assertTrue( persistenceContext.getCollectionEntries().containsKey( publisher.getAuthors() ) ); assertTrue( persistenceContext.getCollectionEntries().containsKey( author2.getBooks() ) ); @@ -139,7 +139,7 @@ public class TestAutoFlushBeforeQueryExecution extends BaseCoreFunctionalTestCas final PersistenceContext persistenceContext = ( (SessionImplementor) s ).getPersistenceContext(); final ActionQueue actionQueue = ( (SessionImpl) s ).getActionQueue(); - assertEquals( 1, persistenceContext.getCollectionEntries().size() ); + assertEquals( 1, persistenceContext.getCollectionEntriesSize() ); assertEquals( 1, persistenceContext.getCollectionsByKey().size() ); assertTrue( persistenceContext.getCollectionEntries().containsKey( publisher.getAuthors() ) ); assertTrue( persistenceContext.getCollectionsByKey().values().contains( publisher.getAuthors() ) ); @@ -149,7 +149,7 @@ public class TestAutoFlushBeforeQueryExecution extends BaseCoreFunctionalTestCas author1.setPublisher( publisher ); publisher.getAuthors().add( author1 ); assertTrue( s.createQuery( "from UnrelatedEntity" ).list().size() == 1 ); - assertEquals( 2, persistenceContext.getCollectionEntries().size() ); + assertEquals( 2, persistenceContext.getCollectionEntriesSize() ); assertEquals( 1, persistenceContext.getCollectionsByKey().size() ); assertTrue( persistenceContext.getCollectionEntries().containsKey( publisher.getAuthors() ) ); assertTrue( persistenceContext.getCollectionEntries().containsKey( author1.getBooks() ) ); @@ -161,7 +161,7 @@ public class TestAutoFlushBeforeQueryExecution extends BaseCoreFunctionalTestCas publisher.getAuthors().clear(); assertEquals( 0, actionQueue.numberOfCollectionRemovals() ); assertTrue( s.createQuery( "from UnrelatedEntity" ).list().size() == 1 ); - assertEquals( 2, persistenceContext.getCollectionEntries().size() ); + assertEquals( 2, persistenceContext.getCollectionEntriesSize() ); assertEquals( 1, persistenceContext.getCollectionsByKey().size() ); assertTrue( persistenceContext.getCollectionEntries().containsKey( publisher.getAuthors() ) ); assertTrue( persistenceContext.getCollectionEntries().containsKey( author1.getBooks() ) ); @@ -176,7 +176,7 @@ public class TestAutoFlushBeforeQueryExecution extends BaseCoreFunctionalTestCas publisher.getAuthors().add( author2 ); List results = s.createQuery( "from UnrelatedEntity" ).list(); assertEquals( 1, results.size() ); - assertEquals( 4, persistenceContext.getCollectionEntries().size() ); + assertEquals( 4, persistenceContext.getCollectionEntriesSize() ); assertEquals( 1, persistenceContext.getCollectionsByKey().size() ); assertTrue( persistenceContext.getCollectionEntries().containsKey( publisher.getAuthors() ) ); assertTrue( persistenceContext.getCollectionEntries().containsKey( author2.getBooks() ) ); @@ -186,7 +186,7 @@ public class TestAutoFlushBeforeQueryExecution extends BaseCoreFunctionalTestCas assertEquals( 0, actionQueue.numberOfCollectionRemovals() ); s.flush(); - assertEquals( 2, persistenceContext.getCollectionEntries().size() ); + assertEquals( 2, persistenceContext.getCollectionEntriesSize() ); assertEquals( 2, persistenceContext.getCollectionsByKey().size() ); assertTrue( persistenceContext.getCollectionEntries().containsKey( publisher.getAuthors() ) ); assertTrue( persistenceContext.getCollectionEntries().containsKey( author2.getBooks() ) ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/EntityJoinTest.java b/hibernate-core/src/test/java/org/hibernate/test/hql/EntityJoinTest.java index e140b5b091..7efff1e585 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/hql/EntityJoinTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/EntityJoinTest.java @@ -16,9 +16,11 @@ import javax.persistence.ManyToOne; import javax.persistence.Table; import org.hibernate.annotations.NaturalId; +import org.hibernate.dialect.SybaseDialect; import org.hibernate.engine.query.spi.HQLQueryPlan; import org.hibernate.hql.spi.QueryTranslator; +import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; import org.junit.After; @@ -105,6 +107,7 @@ public class EntityJoinTest extends BaseNonConfigCoreFunctionalTestCase { @Test @TestForIssue(jiraKey = "HHH-11337") + @SkipForDialect(SybaseDialect.class) public void testLeftOuterEntityJoinsWithImplicitInnerJoinInSelectClause() { doInHibernate( this::sessionFactory, session -> { // this should get all financial records even if their lastUpdateBy user is null diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/HQLTest.java b/hibernate-core/src/test/java/org/hibernate/test/hql/HQLTest.java index 1e84587643..4d256a378f 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/hql/HQLTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/HQLTest.java @@ -16,6 +16,7 @@ import java.util.Map; import org.hibernate.QueryException; import org.hibernate.Session; +import org.hibernate.cfg.AvailableSettings; import org.hibernate.dialect.AbstractHANADialect; import org.hibernate.dialect.DB2Dialect; import org.hibernate.dialect.H2Dialect; @@ -154,7 +155,7 @@ public class HQLTest extends QueryTranslatorTestCase { assertTranslation( "from Animal a where a.offspring.description = 'xyz'" ); assertTranslation( "from Animal a where a.offspring.father.description = 'xyz'" ); } - + @Test @FailureExpected( jiraKey = "N/A", message = "Lacking ClassicQueryTranslatorFactory support" ) public void testRowValueConstructorSyntaxInInList2() { @@ -209,7 +210,7 @@ public class HQLTest extends QueryTranslatorTestCase { AST inNode = whereNode.getFirstChild(); assertEquals( message, expected, inNode != null && inNode.getType() == HqlTokenTypes.IN ); } - + @Test public void testSubComponentReferences() { assertTranslation( "select c.address.zip.code from ComponentContainer c" ); @@ -227,7 +228,7 @@ public class HQLTest extends QueryTranslatorTestCase { public void testJoinFetchCollectionOfValues() { assertTranslation( "select h from Human as h join fetch h.nickNames" ); } - + @Test public void testCollectionMemberDeclarations2() { assertTranslation( "from Customer c, in(c.orders) o" ); @@ -242,11 +243,13 @@ public class HQLTest extends QueryTranslatorTestCase { // IN asks an alias, but the difference is that the error message from AST // contains the error token location (by lines and columns), which is hardly // to get from Classic query translator --stliu - assertTranslation( "from Customer c, in(c.orders)" ); + assertTranslation( "from Customer c, in(c.orders)" ); } @Test public void testCollectionJoinsInSubselect() { + disableOmittingJoinOfSuperclassTables(); + // caused by some goofiness in FromElementFactory that tries to // handle correlated subqueries (but fails miserably) even though this // is not a correlated subquery. HHH-1248 @@ -340,10 +343,14 @@ public class HQLTest extends QueryTranslatorTestCase { @Test public void testImplicitJoinsAlongWithCartesianProduct() { - DotNode.useThetaStyleImplicitJoins = true; - assertTranslation( "select foo.foo from Foo foo, Foo foo2" ); - assertTranslation( "select foo.foo.foo from Foo foo, Foo foo2" ); - DotNode.useThetaStyleImplicitJoins = false; + boolean originalValue = DotNode.useThetaStyleImplicitJoins; + try { + DotNode.useThetaStyleImplicitJoins = true; + assertTranslation("select foo.foo from Foo foo, Foo foo2"); + assertTranslation("select foo.foo.foo from Foo foo, Foo foo2"); + } finally { + DotNode.useThetaStyleImplicitJoins = originalValue; + } } @Test @@ -545,18 +552,22 @@ public class HQLTest extends QueryTranslatorTestCase { @Test public void testCrazyIdFieldNames() { - DotNode.useThetaStyleImplicitJoins = true; - // only regress against non-scalar forms as there appears to be a bug in the classic translator - // in regards to this issue also. Specifically, it interprets the wrong return type, though it gets - // the sql "correct" :/ + boolean originalValue = DotNode.useThetaStyleImplicitJoins; + try { + DotNode.useThetaStyleImplicitJoins = true; + // only regress against non-scalar forms as there appears to be a bug in the classic translator + // in regards to this issue also. Specifically, it interprets the wrong return type, though it gets + // the sql "correct" :/ - String hql = "select e.heresAnotherCrazyIdFieldName from MoreCrazyIdFieldNameStuffEntity e where e.heresAnotherCrazyIdFieldName is not null"; - assertTranslation( hql, new HashMap(), false, null ); + String hql = "select e.heresAnotherCrazyIdFieldName from MoreCrazyIdFieldNameStuffEntity e where e.heresAnotherCrazyIdFieldName is not null"; + assertTranslation(hql, new HashMap(), false, null); - hql = "select e.heresAnotherCrazyIdFieldName.heresAnotherCrazyIdFieldName from MoreCrazyIdFieldNameStuffEntity e where e.heresAnotherCrazyIdFieldName is not null"; - assertTranslation( hql, new HashMap(), false, null ); + hql = "select e.heresAnotherCrazyIdFieldName.heresAnotherCrazyIdFieldName from MoreCrazyIdFieldNameStuffEntity e where e.heresAnotherCrazyIdFieldName is not null"; + assertTranslation(hql, new HashMap(), false, null); - DotNode.useThetaStyleImplicitJoins = false; + } finally { + DotNode.useThetaStyleImplicitJoins = originalValue; + } } @Test @@ -784,7 +795,7 @@ public class HQLTest extends QueryTranslatorTestCase { || getDialect() instanceof Sybase11Dialect || getDialect() instanceof SybaseASE15Dialect || getDialect() instanceof SybaseAnywhereDialect - || getDialect() instanceof SQLServerDialect + || getDialect() instanceof SQLServerDialect || getDialect() instanceof IngresDialect) { // SybaseASE15Dialect and SybaseAnywhereDialect support '||' // MySQL uses concat(x, y, z) @@ -878,6 +889,8 @@ public class HQLTest extends QueryTranslatorTestCase { @Test public void testGroupByFunction() { + disableOmittingJoinOfSuperclassTables(); + if ( getDialect() instanceof Oracle8iDialect ) return; // the new hiearchy... if ( getDialect() instanceof PostgreSQLDialect || getDialect() instanceof PostgreSQL81Dialect ) return; if ( getDialect() instanceof TeradataDialect) return; @@ -1027,26 +1040,36 @@ public class HQLTest extends QueryTranslatorTestCase { @Test public void testImplicitJoinInSelect() { assertTranslation( "select foo, foo.long from Foo foo" ); - DotNode.useThetaStyleImplicitJoins = true; - assertTranslation( "select foo.foo from Foo foo" ); - assertTranslation( "select foo, foo.foo from Foo foo" ); - assertTranslation( "select foo.foo from Foo foo where foo.foo is not null" ); - DotNode.useThetaStyleImplicitJoins = false; + boolean originalValue = DotNode.useThetaStyleImplicitJoins; + try { + DotNode.useThetaStyleImplicitJoins = true; + assertTranslation("select foo.foo from Foo foo"); + assertTranslation("select foo, foo.foo from Foo foo"); + assertTranslation("select foo.foo from Foo foo where foo.foo is not null"); + } finally { + DotNode.useThetaStyleImplicitJoins = originalValue; + } } @Test public void testSelectExpressions() { - DotNode.useThetaStyleImplicitJoins = true; - assertTranslation( "select an.mother.mother from Animal an" ); - assertTranslation( "select an.mother.mother.mother from Animal an" ); - assertTranslation( "select an.mother.mother.bodyWeight from Animal an" ); - assertTranslation( "select an.mother.zoo.id from Animal an" ); - assertTranslation( "select user.human.zoo.id from User user" ); - assertTranslation( "select u.userName, u.human.name.first from User u" ); - assertTranslation( "select u.human.name.last, u.human.name.first from User u" ); - assertTranslation( "select bar.baz.name from Bar bar" ); - assertTranslation( "select bar.baz.name, bar.baz.count from Bar bar" ); - DotNode.useThetaStyleImplicitJoins = false; + disableOmittingJoinOfSuperclassTables(); + + boolean originalValue = DotNode.useThetaStyleImplicitJoins; + try { + DotNode.useThetaStyleImplicitJoins = true; + assertTranslation("select an.mother.mother from Animal an"); + assertTranslation("select an.mother.mother.mother from Animal an"); + assertTranslation("select an.mother.mother.bodyWeight from Animal an"); + assertTranslation("select an.mother.zoo.id from Animal an"); + assertTranslation("select user.human.zoo.id from User user"); + assertTranslation("select u.userName, u.human.name.first from User u"); + assertTranslation("select u.human.name.last, u.human.name.first from User u"); + assertTranslation("select bar.baz.name from Bar bar"); + assertTranslation("select bar.baz.name, bar.baz.count from Bar bar"); + } finally { + DotNode.useThetaStyleImplicitJoins = originalValue; + } } @Test @@ -1102,10 +1125,14 @@ public class HQLTest extends QueryTranslatorTestCase { @Test public void testSelectEntityProperty() throws Exception { - DotNode.useThetaStyleImplicitJoins = true; - assertTranslation( "select an.mother from Animal an" ); - assertTranslation( "select an, an.mother from Animal an" ); - DotNode.useThetaStyleImplicitJoins = false; + boolean originalValue = DotNode.useThetaStyleImplicitJoins; + try { + DotNode.useThetaStyleImplicitJoins = true; + assertTranslation("select an.mother from Animal an"); + assertTranslation("select an, an.mother from Animal an"); + } finally { + DotNode.useThetaStyleImplicitJoins = originalValue; + } } @Test @@ -1202,9 +1229,13 @@ public class HQLTest extends QueryTranslatorTestCase { @Test public void testManyToManyJoinInSubselect() throws Exception { - DotNode.useThetaStyleImplicitJoins = true; - assertTranslation( "select foo from Foo foo where foo in (select elt from Baz baz join baz.fooArray elt)" ); - DotNode.useThetaStyleImplicitJoins = false; + boolean originalValue = DotNode.useThetaStyleImplicitJoins; + try { + DotNode.useThetaStyleImplicitJoins = true; + assertTranslation("select foo from Foo foo where foo in (select elt from Baz baz join baz.fooArray elt)"); + } finally { + DotNode.useThetaStyleImplicitJoins = originalValue; + } } @Test @@ -1258,6 +1289,8 @@ public class HQLTest extends QueryTranslatorTestCase { @Test public void testSelectDialectFunction() throws Exception { + disableOmittingJoinOfSuperclassTables(); + // From SQLFunctionsTest.testDialectSQLFunctions... if ( getDialect() instanceof HSQLDialect ) { assertTranslation( "select mod(s.count, 2) from org.hibernate.test.legacy.Simple as s where s.id = 10" ); @@ -1358,12 +1391,16 @@ public class HQLTest extends QueryTranslatorTestCase { @Test public void testOneToOne() throws Exception { + disableOmittingJoinOfSuperclassTables(); + assertTranslation( "from User u where u.human.nickName='Steve'" ); assertTranslation( "from User u where u.human.name.first='Steve'" ); } @Test public void testSelectClauseImplicitJoin() throws Exception { + disableOmittingJoinOfSuperclassTables(); + //assertTranslation( "select d.owner.mother from Dog d" ); //bug in old qt assertTranslation( "select d.owner.mother.description from Dog d" ); //assertTranslation( "select d.owner.mother from Dog d, Dog h" ); @@ -1473,10 +1510,14 @@ public class HQLTest extends QueryTranslatorTestCase { @Test public void testJoinInSubselect() throws Exception { //new parser uses ANSI-style inner join syntax - DotNode.useThetaStyleImplicitJoins = true; - assertTranslation( "from Animal a where a in (select m from Animal an join an.mother m)" ); - assertTranslation( "from Animal a where a in (select o from Animal an join an.offspring o)" ); - DotNode.useThetaStyleImplicitJoins = false; + boolean originalValue = DotNode.useThetaStyleImplicitJoins; + try { + DotNode.useThetaStyleImplicitJoins = true; + assertTranslation("from Animal a where a in (select m from Animal an join an.mother m)"); + assertTranslation("from Animal a where a in (select o from Animal an join an.offspring o)"); + } finally { + DotNode.useThetaStyleImplicitJoins = originalValue; + } } @Test @@ -1519,12 +1560,16 @@ public class HQLTest extends QueryTranslatorTestCase { @Test public void testManyToManyInJoin() throws Exception { + disableOmittingJoinOfSuperclassTables(); + assertTranslation( "select x.id from Human h1 join h1.family x" ); //assertTranslation("select index(h2) from Human h1 join h1.family h2"); } @Test public void testManyToManyInSubselect() throws Exception { + disableOmittingJoinOfSuperclassTables(); + assertTranslation( "from Human h1, Human h2 where h2 in (select x.id from h1.family x)" ); assertTranslation( "from Human h1, Human h2 where 'father' in indices(h1.family)" ); } @@ -1580,4 +1625,12 @@ public class HQLTest extends QueryTranslatorTestCase { compileWithAstQueryTranslator( "from Human where name.first = 'Gavin'", false ); } + private void disableOmittingJoinOfSuperclassTables() { + // Disable this feature because of Lacking ClassicQueryTranslatorFactory support + rebuildSessionFactory( c -> c.setProperty( + AvailableSettings.OMIT_JOIN_OF_SUPERCLASS_TABLES, + Boolean.FALSE.toString() + ) ); + } + } diff --git a/hibernate-core/src/test/java/org/hibernate/test/joinwithoutancestor/OmitAncestorJoinTest.java b/hibernate-core/src/test/java/org/hibernate/test/joinwithoutancestor/OmitAncestorJoinTest.java new file mode 100644 index 0000000000..fee912f180 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/joinwithoutancestor/OmitAncestorJoinTest.java @@ -0,0 +1,167 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.joinwithoutancestor; + +import java.util.List; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +@TestForIssue( jiraKey = "HHH-12993") +public class OmitAncestorJoinTest extends OmitAncestorTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { A.class, SubA.class, SubSubA.class, B.class }; + } + + @Test + public void test() { + // Should not join any parent table + assertFromTables("select valSubA from SubA", SubA.TABLE); + + // Should not join any parent table + assertFromTables("select sa.valSubA from SubA sa", SubA.TABLE); + + // Should omit middle table from inheritance hierarchy + assertFromTables("select ssa.valA from SubSubA ssa", SubSubA.TABLE, A.TABLE); + + // Should omit middle table from inheritance hierarchy + assertFromTables( "select ssa.valA, ssa.valSubSubA from SubSubA ssa", SubSubA.TABLE, A.TABLE ); + + // Should join parent table, because it is used in "where" part + assertFromTables("select valSubA from SubA where valA = 'foo'", SubA.TABLE, A.TABLE); + + // Should join parent table, because it is used in "order by" part + assertFromTables("select valSubSubA from SubSubA order by valA", SubSubA.TABLE, A.TABLE); + + // Should other tables from hierarchye, because it returns whole entity + assertFromTables("select suba from SubA suba", SubA.TABLE, A.TABLE, SubSubA.TABLE); + assertFromTables("from SubA", SubA.TABLE, A.TABLE, SubSubA.TABLE); + + // Should join A table, because it has the reference to B table + assertFromTables( "select suba.b from SubA suba", SubA.TABLE, A.TABLE, B.TABLE ); + assertFromTables( "select suba.b.id from SubA suba", SubA.TABLE, A.TABLE ); + } + + @Entity(name = "A") + @Table(name = A.TABLE) + @Inheritance(strategy = InheritanceType.JOINED) + static class A { + + public static final String TABLE = "A_Table"; + + @Id + @GeneratedValue + private Long id; + + private String valA; + + @ManyToOne + private B b; + + public Long getId() { + return id; + } + + public String getValA() { + return valA; + } + + public void setValA(String valA) { + this.valA = valA; + } + + public B getB() { + return b; + } + + public void setB(B b) { + this.b = b; + } + } + + @Entity(name = "SubA") + @Table(name = SubA.TABLE) + static class SubA extends A { + + public static final String TABLE = "SubA_Table"; + + private String valSubA; + + public String getValSubA() { + return valSubA; + } + + public void setValSubA(String valSubA) { + this.valSubA = valSubA; + } + } + + @Entity(name = "SubSubA") + @Table(name = SubSubA.TABLE) + static class SubSubA extends SubA { + + public static final String TABLE = "SubSubA_Table"; + + private String valSubSubA; + + public String getValSubSubA() { + return valSubSubA; + } + + public void setValSubSubA(String valSubSubA) { + this.valSubSubA = valSubSubA; + } + } + + @Entity(name = "B") + @Table(name = B.TABLE) + static class B { + + public static final String TABLE = "B_table"; + + @Id + @GeneratedValue + private Long id; + + private String valB; + + @OneToMany(mappedBy = "b") + private List aList; + + public Long getId() { + return id; + } + + public String getValB() { + return valB; + } + + public void setValB(String valB) { + this.valB = valB; + } + + public List getaList() { + return aList; + } + + public void setaList(List aList) { + this.aList = aList; + } + + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/joinwithoutancestor/OmitAncestorJoinWhenCommonSecondaryTablePresentTest.java b/hibernate-core/src/test/java/org/hibernate/test/joinwithoutancestor/OmitAncestorJoinWhenCommonSecondaryTablePresentTest.java new file mode 100644 index 0000000000..70f284e02d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/joinwithoutancestor/OmitAncestorJoinWhenCommonSecondaryTablePresentTest.java @@ -0,0 +1,262 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.joinwithoutancestor; + +import java.util.List; +import javax.persistence.Column; +import javax.persistence.ConstraintMode; +import javax.persistence.Entity; +import javax.persistence.ForeignKey; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.ManyToOne; +import javax.persistence.SecondaryTable; +import javax.persistence.Table; + +import org.hibernate.query.Query; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.transaction.TransactionUtil; +import org.junit.Assert; +import org.junit.Test; + +@TestForIssue(jiraKey = "HHH-12993") +public class OmitAncestorJoinWhenCommonSecondaryTablePresentTest extends OmitAncestorTestCase { + + private static final String SECONDARY_TABLE_NAME = "secondary_table"; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { A.class, SubA.class, B.class, SubB.class, C.class }; + } + + @Override + protected boolean isCleanupTestDataRequired() { + return true; + } + + @Override + protected void cleanupTestData() throws Exception { + TransactionUtil.doInHibernate( this::sessionFactory, s -> { + s.createQuery( "from A", A.class ).list().forEach( s::remove ); + s.createQuery( "from B", B.class ).list().forEach( s::remove ); + s.createQuery( "from C", C.class ).list().forEach( s::remove ); + } ); + super.cleanupTestData(); + } + + @Test + public void shouldNotReturnSecondaryTableValueForSubB() { + TransactionUtil.doInHibernate( this::sessionFactory, session -> { + SubA subA = new SubA( 1L ); + subA.setValSubA( "valSubA" ); + subA.setValSecondaryTable( "valSecondaryTableFromSubA" ); + session.persist( subA ); + + SubB subB = new SubB( 2L ); + subB.setValSubB( "valSubB" ); + subB.setValSecondaryTable( "valSecondaryTableFromSubB" ); + session.persist( subB ); + + Query query = session.createQuery( "select suba.valSecondaryTable from SubA suba", String.class ); + List resultList = query.getResultList(); + Assert.assertEquals( 1, resultList.size() ); + Assert.assertEquals( "valSecondaryTableFromSubA", resultList.get( 0 ) ); + } ); + } + + @Test + public void shouldNotReturnSecondaryTableValueForSubB_implicitJoin() { + TransactionUtil.doInHibernate( this::sessionFactory, session -> { + SubA subA = new SubA( 1L ); + subA.setValSubA( "valSubA" ); + subA.setValSecondaryTable( "valSecondaryTableFromSubA" ); + session.persist( subA ); + + SubB subB = new SubB( 2L ); + subB.setValSubB( "valSubB" ); + subB.setValSecondaryTable( "valSecondaryTableFromSubB" ); + session.persist( subB ); + + C c = new C(); + c.setSubA( subA ); + session.persist( c ); + + Query query = session.createQuery( "select c.subA.valSecondaryTable from C c", String.class ); + List resultList = query.getResultList(); + Assert.assertEquals( 1, resultList.size() ); + Assert.assertEquals( "valSecondaryTableFromSubA", resultList.get( 0 ) ); + } ); + } + + @Entity(name = "A") + @Table(name = A.TABLE) + @Inheritance(strategy = InheritanceType.JOINED) + static class A { + + public static final String TABLE = "A_Table"; + + public A() { + } + + public A(Long id) { + this.id = id; + } + + @Id + private Long id; + + private String valA; + + public Long getId() { + return id; + } + + public String getValA() { + return valA; + } + + public void setValA(String valA) { + this.valA = valA; + } + + } + + @Entity(name = "SubA") + @Table(name = SubA.TABLE) + @SecondaryTable(name = SECONDARY_TABLE_NAME, foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT)) + static class SubA extends A { + + public static final String TABLE = "SubA_table"; + + public SubA() { + } + + public SubA(Long id) { + super( id ); + } + + private String valSubA; + + @Column(table = SECONDARY_TABLE_NAME) + private String valSecondaryTable; + + public String getValSubA() { + return valSubA; + } + + public void setValSubA(String valSubA) { + this.valSubA = valSubA; + } + + public String getValSecondaryTable() { + return valSecondaryTable; + } + + public void setValSecondaryTable(String valSecondaryTable) { + this.valSecondaryTable = valSecondaryTable; + } + } + + @Entity(name = "B") + @Table(name = B.TABLE) + @Inheritance(strategy = InheritanceType.JOINED) + static class B { + + public static final String TABLE = "B_Table"; + + public B() { + } + + public B(Long id) { + this.id = id; + } + + @Id + private Long id; + + private String valB; + + public Long getId() { + return id; + } + + public String getValB() { + return valB; + } + + public void setValB(String valB) { + this.valB = valB; + } + + } + + @Entity(name = "SubB") + @Table(name = SubB.TABLE) + @SecondaryTable(name = SECONDARY_TABLE_NAME, foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT)) + static class SubB extends B { + + public static final String TABLE = "SubB_table"; + + public SubB() { + } + + public SubB(Long id) { + super( id ); + } + + private String valSubB; + + @Column(table = SECONDARY_TABLE_NAME) + private String valSecondaryTable; + + public String getValSubB() { + return valSubB; + } + + public void setValSubB(String valSubB) { + this.valSubB = valSubB; + } + + public String getValSecondaryTable() { + return valSecondaryTable; + } + + public void setValSecondaryTable(String valSecondaryTable) { + this.valSecondaryTable = valSecondaryTable; + } + } + + @Entity(name = "C") + @Table(name = C.TABLE) + static class C { + + public static final String TABLE = "C_table"; + + @Id + @GeneratedValue + private Long id; + + @ManyToOne + private SubA subA; + + public Long getId() { + return id; + } + + public SubA getSubA() { + return subA; + } + + public void setSubA(SubA subA) { + this.subA = subA; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/joinwithoutancestor/OmitAncestorJoinWhenSecondaryTablePresentTest.java b/hibernate-core/src/test/java/org/hibernate/test/joinwithoutancestor/OmitAncestorJoinWhenSecondaryTablePresentTest.java new file mode 100644 index 0000000000..0a5c8a57cb --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/joinwithoutancestor/OmitAncestorJoinWhenSecondaryTablePresentTest.java @@ -0,0 +1,107 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.joinwithoutancestor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.SecondaryTable; +import javax.persistence.Table; + +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +@TestForIssue(jiraKey = "HHH-12993") +public class OmitAncestorJoinWhenSecondaryTablePresentTest extends OmitAncestorTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { A.class, SubA.class, SubSubA.class }; + } + + @Test + public void test() { + assertFromTables( "select valSubASecondaryTable from SubA", SubA.TABLE, SubSubA.TABLE, SubA.SECONDARY_TABLE ); + } + + @Entity(name = "A") + @Table(name = A.TABLE) + @Inheritance(strategy = InheritanceType.JOINED) + static class A { + + public static final String TABLE = "A_Table"; + + @Id + @GeneratedValue + private Long id; + + private String valA; + + public Long getId() { + return id; + } + + public String getValA() { + return valA; + } + + public void setValA(String valA) { + this.valA = valA; + } + } + + @Entity(name = "SubA") + @Table(name = SubA.TABLE) + @SecondaryTable(name = SubA.SECONDARY_TABLE) + static class SubA extends A { + + public static final String TABLE = "SubA_Table"; + public static final String SECONDARY_TABLE = "SubA_Table_Sec"; + + private String valSubA; + + @Column(table = SECONDARY_TABLE) + private String valSubASecondaryTable; + + public String getValSubA() { + return valSubA; + } + + public void setValSubA(String valSubA) { + this.valSubA = valSubA; + } + + public String getValSubASecondaryTable() { + return valSubASecondaryTable; + } + + public void setValSubASecondaryTable(String valSubASecondaryTable) { + this.valSubASecondaryTable = valSubASecondaryTable; + } + } + + @Entity(name = "SubSubA") + @Table(name = SubSubA.TABLE) + static class SubSubA extends SubA { + + public static final String TABLE = "SubSubA_Table"; + + private String valSubSubA; + + public String getValSubSubA() { + return valSubSubA; + } + + public void setValSubSubA(String valSubSubA) { + this.valSubSubA = valSubSubA; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/joinwithoutancestor/OmitAncestorTestCase.java b/hibernate-core/src/test/java/org/hibernate/test/joinwithoutancestor/OmitAncestorTestCase.java new file mode 100644 index 0000000000..5c6d310287 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/joinwithoutancestor/OmitAncestorTestCase.java @@ -0,0 +1,48 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.joinwithoutancestor; + +import org.hibernate.Session; +import org.hibernate.engine.query.spi.HQLQueryPlan; +import org.hibernate.engine.query.spi.QueryPlanCache; +import org.hibernate.hql.spi.QueryTranslator; +import org.hibernate.internal.SessionImpl; + +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.testing.transaction.TransactionUtil; + +public abstract class OmitAncestorTestCase extends BaseCoreFunctionalTestCase { + + protected void assertFromTables(String query, String... tables) { + try { + TransactionUtil.doInHibernate( this::sessionFactory, session -> { + String sql = getSql( session, query ); + SqlAsserts.assertFromTables( sql, tables ); + session.createQuery( query ).getResultList(); + } ); + } + catch (AssertionError e) { + throw e; + } + } + + protected String getSql(Session session, String hql) { + // Create query + session.createQuery( hql ); + + // Get plan from cache + QueryPlanCache queryPlanCache = sessionFactory().getQueryPlanCache(); + HQLQueryPlan hqlQueryPlan = queryPlanCache.getHQLQueryPlan( + hql, + false, + ( (SessionImpl) session ).getLoadQueryInfluencers().getEnabledFilters() + ); + QueryTranslator queryTranslator = hqlQueryPlan.getTranslators()[0]; + String sql = queryTranslator.getSQLString(); + return sql; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/joinwithoutancestor/SqlAsserts.java b/hibernate-core/src/test/java/org/hibernate/test/joinwithoutancestor/SqlAsserts.java new file mode 100644 index 0000000000..5932c99352 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/joinwithoutancestor/SqlAsserts.java @@ -0,0 +1,140 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.joinwithoutancestor; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public class SqlAsserts { + + public static void assertFromTables(String sql, String... expectedTables) { + List actualTables = parse( sql ); + if ( expectedTables.length == actualTables.size() ) { + boolean diffFound = false; + for ( int i = 0; i < expectedTables.length; i++ ) { + if ( !( expectedTables[i].equals( actualTables.get( i ).name ) ) ) { + diffFound = true; + break; + } + } + if ( !diffFound ) { + return; + } + } + List actualTableNames = actualTables.stream().map( x -> x.name ).collect( Collectors.toList() ); + List expectedTableNames = Arrays.asList( expectedTables ); + throw new AssertionError( "Expected tables: " + expectedTableNames + ", Actual tables: " + actualTableNames ); + } + + private static List
parse(String sql) { + List
result = new ArrayList<>(); + String from = findFrom( sql ); + List commaSeparatedFromParts = findCommaSeparatedFromParts( from ); + for ( String commaSeparatedFromPart : commaSeparatedFromParts ) { + List
tables = findTables( commaSeparatedFromPart ); + result.addAll( tables ); + } + return result; + } + + private static String findFrom(String sqlString) { + Pattern pattern = Pattern.compile( ".*\\s+from\\s+(?.*?)(\\z|(\\s+(where|order|having).*))" ); + Matcher matcher = pattern.matcher( sqlString ); + if ( matcher.matches() ) { + return matcher.group( "frompart" ); + } + else { + throw new RuntimeException( "Can not find from part in sql statement." ); + } + } + + private static List findCommaSeparatedFromParts(String from) { + return Arrays.stream( from.split( "," ) ).map( x -> x.trim() ).collect( Collectors.toList() ); + } + + private static List
findTables(String fromPart) { + List
result = new ArrayList<>(); + result.add( findFirstTable( fromPart ) ); + + String otherTablesPart = findOtherTablesPart( fromPart ); + result.addAll( findOtherTables( otherTablesPart ) ); + + return result; + } + + private static Table findFirstTable(String fromPart) { + Pattern pattern = Pattern.compile( "(?
\\S+)\\s+(?\\S*)\\s*(?.*)" ); + Matcher matcher = pattern.matcher( fromPart ); + if ( matcher.matches() ) { + Table firstTable = new Table( matcher.group( "table" ), matcher.group( "alias" ), false, false ); + return firstTable; + } + else { + throw new RuntimeException( "Can not find the first table in the from part." ); + } + } + + private static String findOtherTablesPart(String fromPart) { + Pattern pattern = Pattern.compile( "(?
\\S+)\\s+(?\\S*)\\s*(?.*)" ); + Matcher matcher = pattern.matcher( fromPart ); + if ( matcher.matches() ) { + String joins = matcher.group( "joins" ); + return joins; + } + else { + throw new RuntimeException( "Can not find joins in the from part." ); + } + } + + private static List
findOtherTables(String otherTablesPart) { + Pattern pattern = Pattern.compile( + "(?join|inner join|left join|cross join|left outer join)\\s+(?
\\S+)\\s+(?\\S+)" ); + Matcher matcher = pattern.matcher( otherTablesPart ); + List
joins = new ArrayList<>(); + while ( matcher.find() ) { + String table = matcher.group( "table" ); + String alias = matcher.group( "alias" ); + String join = matcher.group( "jointype" ); + boolean innerJoin = join.equals( "join" ) || join.equals( "inner join" ); + joins.add( new Table( table, alias, true, innerJoin ) ); + } + return joins; + } + + private static class Table { + String name; + String alias; + boolean join; + boolean innerJoin; + + public Table(String table, String alias, boolean join, boolean innerJoin) { + this.name = table; + this.alias = alias; + this.join = join; + this.innerJoin = innerJoin; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + if ( innerJoin ) { + sb.append( "inner join " ); + } + else if ( join ) { + sb.append( "join " ); + } + sb.append( name ); + sb.append( " " ); + sb.append( alias ); + return sb.toString(); + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/legacy/CustomPersister.java b/hibernate-core/src/test/java/org/hibernate/test/legacy/CustomPersister.java index d79fa014e8..9615248f4f 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/legacy/CustomPersister.java +++ b/hibernate-core/src/test/java/org/hibernate/test/legacy/CustomPersister.java @@ -71,7 +71,7 @@ public class CustomPersister implements EntityPersister { NaturalIdDataAccess naturalIdRegionAccessStrategy, PersisterCreationContext creationContext) { this.factory = creationContext.getSessionFactory(); - this.entityMetamodel = new EntityMetamodel( model, this, creationContext ); + this.entityMetamodel = new EntityMetamodel( model, this, factory ); } public boolean hasLazyProperties() { diff --git a/hibernate-core/src/test/java/org/hibernate/test/legacy/FumTest.java b/hibernate-core/src/test/java/org/hibernate/test/legacy/FumTest.java index c858cea453..7fffc1a267 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/legacy/FumTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/legacy/FumTest.java @@ -8,11 +8,6 @@ //$Id: FumTest.java 10977 2006-12-12 23:28:04Z steve.ebersole@jboss.com $ package org.hibernate.test.legacy; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -21,7 +16,6 @@ import java.io.ObjectOutputStream; import java.io.Serializable; import java.sql.SQLException; import java.util.ArrayList; -import java.util.Calendar; import java.util.Date; import java.util.HashSet; import java.util.Iterator; @@ -51,18 +45,22 @@ import org.hibernate.dialect.MySQLDialect; import org.hibernate.dialect.PointbaseDialect; import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.dialect.TimesTenDialect; - -import org.hibernate.testing.DialectChecks; -import org.hibernate.testing.RequiresDialectFeature; -import org.hibernate.testing.SkipForDialect; import org.hibernate.transform.Transformers; -import org.hibernate.type.CalendarType; import org.hibernate.type.EntityType; import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.StringType; import org.hibernate.type.Type; + +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.SkipForDialect; import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + @RequiresDialectFeature(DialectChecks.SupportsNoColumnInsert.class) public class FumTest extends LegacyTestCase { private static short fumKeyShort = 1; @@ -470,13 +468,13 @@ public class FumTest extends LegacyTestCase { ); Query qu = s.createQuery("select fum.fum, fum , fum.fum from Fum fum"); Type[] types = qu.getReturnTypes(); - assertTrue(types.length==3); - for ( int k=0; k { @@ -92,6 +96,7 @@ public class LockModeTest extends BaseCoreFunctionalTestCase { } @Test + @RequiresDialectFeature( value = DialectChecks.SupportsLockTimeouts.class ) public void testLegacyCriteriaAliasSpecific() { // open a session, begin a transaction and lock row doInHibernate( this::sessionFactory, session -> { @@ -108,6 +113,7 @@ public class LockModeTest extends BaseCoreFunctionalTestCase { } @Test + @RequiresDialectFeature( value = DialectChecks.SupportsLockTimeouts.class ) public void testQuery() { // open a session, begin a transaction and lock row doInHibernate( this::sessionFactory, session -> { diff --git a/hibernate-core/src/test/java/org/hibernate/test/naturalid/lazy/NaturalIdInUninitializedAssociationTest.java b/hibernate-core/src/test/java/org/hibernate/test/naturalid/lazy/NaturalIdInUninitializedAssociationTest.java new file mode 100644 index 0000000000..f17d4baed4 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/naturalid/lazy/NaturalIdInUninitializedAssociationTest.java @@ -0,0 +1,173 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.naturalid.lazy; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.NaturalId; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +/** + * @author Gail Badner + */ +@SuppressWarnings({"unused", "WeakerAccess","ResultOfMethodCallIgnored"}) +@TestForIssue( jiraKey = "HHH-13607" ) +public class NaturalIdInUninitializedAssociationTest extends BaseNonConfigCoreFunctionalTestCase { + + @Test + public void testImmutableNaturalId() { + inTransaction( + session -> { + final AnEntity e = session.get( AnEntity.class, 3 ); + assertFalse( Hibernate.isInitialized( e.entityImmutableNaturalId ) ); + } + ); + + inTransaction( + session -> { + final AnEntity e = session.get( AnEntity.class, 3 ); + Hibernate.initialize( e.entityImmutableNaturalId ); + assertEquals( "immutable name", e.entityImmutableNaturalId.getName() ); + } + ); + } + + @Test + public void testMutableNaturalId() { + inTransaction( + session -> { + final AnEntity e = session.get( AnEntity.class, 3 ); + assertFalse( Hibernate.isInitialized( e.entityMutableNaturalId ) ); + } + ); + + inTransaction( + session -> { + final AnEntity e = session.get( AnEntity.class, 3 ); + assertEquals( "mutable name", e.entityMutableNaturalId.getName() ); + } + ); + } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.FORMAT_SQL, "false" ); + } + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + super.configureSessionFactoryBuilder( sfb ); + sfb.applyStatisticsSupport( true ); + sfb.applySecondLevelCacheSupport( false ); + sfb.applyQueryCacheSupport( false ); + } + + @Override + protected void applyMetadataSources(MetadataSources sources) { + super.applyMetadataSources( sources ); + sources.addAnnotatedClass( AnEntity.class ); + sources.addAnnotatedClass( EntityMutableNaturalId.class ); + sources.addAnnotatedClass( EntityImmutableNaturalId.class ); + } + + @Before + public void prepareTestData() { + inTransaction( + session -> { + EntityMutableNaturalId entityMutableNaturalId = new EntityMutableNaturalId( 1, "mutable name" ); + EntityImmutableNaturalId entityImmutableNaturalId = new EntityImmutableNaturalId( 2, "immutable name" ); + AnEntity anEntity = new AnEntity(); + anEntity.id = 3; + anEntity.entityImmutableNaturalId = entityImmutableNaturalId; + anEntity.entityMutableNaturalId = entityMutableNaturalId; + session.persist( anEntity ); + } + ); + } + + @After + public void cleanUpTestData() { + inTransaction( + session -> { + session.delete( session.get( AnEntity.class, 3 ) ); + } + ); + } + + @Entity(name = "AnEntity") + public static class AnEntity { + @Id + private int id; + + @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) + private EntityMutableNaturalId entityMutableNaturalId; + + @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) + private EntityImmutableNaturalId entityImmutableNaturalId; + } + + @Entity(name = "EntityMutableNaturalId") + public static class EntityMutableNaturalId { + @Id + private int id; + + @NaturalId(mutable = true) + private String name; + + public EntityMutableNaturalId() { + } + + public EntityMutableNaturalId(int id, String name) { + this.id = id; + this.name = name; + } + + public String getName() { + return name; + } + } + + @Entity(name = "EntityImmutableNaturalId") + public static class EntityImmutableNaturalId { + @Id + private int id; + + @NaturalId(mutable = false) + private String name; + + public EntityImmutableNaturalId() { + } + + public EntityImmutableNaturalId(int id, String name) { + this.id = id; + this.name = name; + } + + public String getName() { + return name; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/naturalid/lazy/NaturalIdInUninitializedProxyTest.java b/hibernate-core/src/test/java/org/hibernate/test/naturalid/lazy/NaturalIdInUninitializedProxyTest.java new file mode 100644 index 0000000000..b73ce854f7 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/naturalid/lazy/NaturalIdInUninitializedProxyTest.java @@ -0,0 +1,144 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.naturalid.lazy; + +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.NaturalId; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +/** + * @author Gail Badner + */ +@SuppressWarnings({"unused", "WeakerAccess","ResultOfMethodCallIgnored"}) +@TestForIssue( jiraKey = "HHH-13607" ) +public class NaturalIdInUninitializedProxyTest extends BaseNonConfigCoreFunctionalTestCase { + + @Test + public void testImmutableNaturalId() { + inTransaction( + session -> { + final EntityImmutableNaturalId e = session.getReference( EntityImmutableNaturalId.class, 1 ); + assertFalse( Hibernate.isInitialized( e ) ); + } + ); + + inTransaction( + session -> { + final EntityImmutableNaturalId e = session.get( EntityImmutableNaturalId.class, 1 ); + assertEquals( "name", e.name ); + } + ); + } + + @Test + public void testMutableNaturalId() { + inTransaction( + session -> { + final EntityMutableNaturalId e = session.getReference( EntityMutableNaturalId.class, 1 ); + assertFalse( Hibernate.isInitialized( e ) ); + } + ); + + inTransaction( + session -> { + final EntityMutableNaturalId e = session.get( EntityMutableNaturalId.class, 1 ); + assertEquals( "name", e.name ); + } + ); + } + + @Override + protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) { + super.configureStandardServiceRegistryBuilder( ssrb ); + ssrb.applySetting( AvailableSettings.FORMAT_SQL, "false" ); + } + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + super.configureSessionFactoryBuilder( sfb ); + sfb.applyStatisticsSupport( true ); + sfb.applySecondLevelCacheSupport( false ); + sfb.applyQueryCacheSupport( false ); + } + + @Override + protected void applyMetadataSources(MetadataSources sources) { + super.applyMetadataSources( sources ); + sources.addAnnotatedClass( EntityMutableNaturalId.class ); + sources.addAnnotatedClass( EntityImmutableNaturalId.class ); + } + + @Before + public void prepareTestData() { + inTransaction( + session -> { + session.persist( new EntityMutableNaturalId( 1, "name" ) ); + session.persist( new EntityImmutableNaturalId( 1, "name" ) ); + } + ); + } + + @After + public void cleanUpTestData() { + inTransaction( + session -> { + session.createQuery( "delete from EntityMutableNaturalId" ).executeUpdate(); + session.createQuery( "delete from EntityImmutableNaturalId" ).executeUpdate(); + } + ); + } + + @Entity(name = "EntityMutableNaturalId") + public static class EntityMutableNaturalId { + @Id + private int id; + + @NaturalId(mutable = true) + private String name; + + public EntityMutableNaturalId() { + } + + public EntityMutableNaturalId(int id, String name) { + this.id = id; + this.name = name; + } + } + + @Entity(name = "EntityImmutableNaturalId") + public static class EntityImmutableNaturalId { + @Id + private int id; + + @NaturalId(mutable = false) + private String name; + + public EntityImmutableNaturalId() { + } + + public EntityImmutableNaturalId(int id, String name) { + this.id = id; + this.name = name; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/resource/transaction/jdbc/autocommit/MySQLSkipAutoCommitTest.java b/hibernate-core/src/test/java/org/hibernate/test/resource/transaction/jdbc/autocommit/MySQLSkipAutoCommitTest.java index b8f61a87f7..a417dc99fb 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/resource/transaction/jdbc/autocommit/MySQLSkipAutoCommitTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/resource/transaction/jdbc/autocommit/MySQLSkipAutoCommitTest.java @@ -30,11 +30,20 @@ public class MySQLSkipAutoCommitTest extends AbstractSkipAutoCommitTest { if ( getDialect() instanceof MariaDBDialect ) { dataSource = ReflectionUtil.newInstance( "org.mariadb.jdbc.MariaDbDataSource" ); } - else if ( getDialect() instanceof MySQL8Dialect ) { - dataSource = ReflectionUtil.newInstance( "com.mysql.cj.jdbc.MysqlDataSource" ); - } else if ( getDialect() instanceof MySQLDialect ) { - dataSource = ReflectionUtil.newInstance( "com.mysql.jdbc.jdbc2.optional.MysqlDataSource" ); + try { + // ConnectorJ 8 + dataSource = ReflectionUtil.newInstance( "com.mysql.cj.jdbc.MysqlDataSource" ); + } + catch (IllegalArgumentException e) { + try { + // ConnectorJ 5 + dataSource = ReflectionUtil.newInstance( "com.mysql.jdbc.jdbc2.optional.MysqlDataSource" ); + } catch (Exception e2) { + e2.addSuppressed( e ); + throw e; + } + } } ReflectionUtil.setProperty( dataSource, "url", Environment.getProperties().getProperty( AvailableSettings.URL ) ); ReflectionUtil.setProperty( dataSource, "user", Environment.getProperties().getProperty( AvailableSettings.USER ) ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/ImplicitCompositeKeyJoinTest.java b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/ImplicitCompositeKeyJoinTest.java index 4743098e7c..4f59647beb 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/ImplicitCompositeKeyJoinTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/schemaupdate/ImplicitCompositeKeyJoinTest.java @@ -6,16 +6,14 @@ */ package org.hibernate.test.schemaupdate; +import java.io.Serializable; +import java.util.List; import javax.persistence.Column; import javax.persistence.Embeddable; import javax.persistence.EmbeddedId; import javax.persistence.Entity; import javax.persistence.ManyToOne; import javax.persistence.Table; -import java.io.Serializable; -import java.util.List; - -import org.apache.log4j.Logger; import org.hibernate.annotations.ForeignKey; import org.hibernate.boot.MetadataSources; @@ -23,9 +21,10 @@ import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.tool.schema.internal.SchemaCreatorImpl; +import org.hibernate.testing.TestForIssue; import org.junit.Test; -import org.hibernate.testing.TestForIssue; +import org.apache.log4j.Logger; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -57,8 +56,8 @@ public class ImplicitCompositeKeyJoinTest { if ( command.toLowerCase().matches( "^create( (column|row))? table employee.+" ) ) { final String[] columnsDefinition = getColumnsDefinition( command ); - for ( int i = 0; i < columnsDefinition.length; i++ ) { - checkColumnSize( columnsDefinition[i] ); + for ( String columnsDefinition1 : columnsDefinition ) { + checkColumnSize( columnsDefinition1 ); } createTableEmployeeFound = true; } diff --git a/hibernate-core/src/test/java/org/hibernate/test/stateless/fetching/StatelessSessionFetchingTest.java b/hibernate-core/src/test/java/org/hibernate/test/stateless/fetching/StatelessSessionFetchingTest.java index 01f7d0e75f..1b058e74e5 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/stateless/fetching/StatelessSessionFetchingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/stateless/fetching/StatelessSessionFetchingTest.java @@ -18,6 +18,7 @@ import org.hibernate.StatelessSession; import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl; import org.hibernate.cfg.Configuration; +import org.hibernate.dialect.DB2Dialect; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.internal.util.StringHelper; @@ -224,7 +225,18 @@ public class StatelessSessionFetchingTest extends BaseCoreFunctionalTestCase { ss.beginTransaction(); final Query query = ss.createQuery( "select p from Producer p join fetch p.products" ); - final ScrollableResults scrollableResults = query.scroll(ScrollMode.FORWARD_ONLY); + ScrollableResults scrollableResults = null; + if ( getDialect() instanceof DB2Dialect ) { + /* + FetchingScrollableResultsImp#next() in order to check if the ResultSet is empty calls ResultSet#isBeforeFirst() + but the support for ResultSet#isBeforeFirst() is optional for ResultSets with a result + set type of TYPE_FORWARD_ONLY and db2 does not support it. + */ + scrollableResults = query.scroll( ScrollMode.SCROLL_INSENSITIVE ); + } + else { + scrollableResults = query.scroll( ScrollMode.FORWARD_ONLY ); + } while ( scrollableResults.next() ) { Producer producer = (Producer) scrollableResults.get( 0 ); assertTrue( Hibernate.isInitialized( producer ) ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/subselectfetch/SubselectFetchCollectionFromBatchTest.java b/hibernate-core/src/test/java/org/hibernate/test/subselectfetch/SubselectFetchCollectionFromBatchTest.java index b0c75e9358..82e3cdca7a 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/subselectfetch/SubselectFetchCollectionFromBatchTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/subselectfetch/SubselectFetchCollectionFromBatchTest.java @@ -35,8 +35,6 @@ import javax.persistence.ManyToOne; import javax.persistence.OneToMany; import javax.persistence.Table; -import org.junit.Test; - import org.hibernate.Hibernate; import org.hibernate.Session; import org.hibernate.Transaction; @@ -45,8 +43,10 @@ import org.hibernate.annotations.Fetch; import org.hibernate.annotations.FetchMode; import org.hibernate.cfg.Configuration; import org.hibernate.cfg.Environment; + import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -102,22 +102,22 @@ public class SubselectFetchCollectionFromBatchTest extends BaseCoreFunctionalTe assertEquals( 0, sessionFactory().getStatistics().getPrepareStatementCount() ); - for (int i = 0 ; i < groups.length; i++ ) { + for ( EmployeeGroup group : groups ) { // Both groups get initialized and are added to the PersistenceContext when i == 0; // Still need to call Hibernate.initialize( groups[i] ) for i > 0 so that the entity // in the PersistenceContext gets assigned to its respective proxy target (is this a // bug???) - Hibernate.initialize( groups[ i ] ); - assertTrue( Hibernate.isInitialized( groups[i] ) ); + Hibernate.initialize( group ); + assertTrue( Hibernate.isInitialized( group ) ); // the collections should be uninitialized - assertFalse( Hibernate.isInitialized( groups[i].getEmployees() ) ); + assertFalse( Hibernate.isInitialized( group.getEmployees() ) ); } // both Group proxies should have been loaded in the same batch; assertEquals( 1, sessionFactory().getStatistics().getPrepareStatementCount() ); sessionFactory().getStatistics().clear(); - for (EmployeeGroup group : groups) { + for ( EmployeeGroup group : groups ) { assertTrue( Hibernate.isInitialized( group ) ); assertFalse( Hibernate.isInitialized( group.getEmployees() ) ); } @@ -250,26 +250,26 @@ public class SubselectFetchCollectionFromBatchTest extends BaseCoreFunctionalTe assertEquals( 0, sessionFactory().getStatistics().getPrepareStatementCount() ); - for (int i = 0 ; i < groups.length; i++ ) { + for ( EmployeeGroup group : groups ) { // Both groups get initialized and are added to the PersistenceContext when i == 0; // Still need to call Hibernate.initialize( groups[i] ) for i > 0 so that the entity // in the PersistenceContext gets assigned to its respective proxy target (is this a // bug???) - Hibernate.initialize( groups[ i ] ); - assertTrue( Hibernate.isInitialized( groups[i] ) ); - assertTrue( Hibernate.isInitialized( groups[i].getLead() ) ); - assertFalse( Hibernate.isInitialized( groups[i].getLead().getCollaborators() ) ); - assertTrue( Hibernate.isInitialized( groups[i].getManager() ) ); - assertFalse( Hibernate.isInitialized( groups[i].getManager().getCollaborators() ) ); + Hibernate.initialize( group ); + assertTrue( Hibernate.isInitialized( group ) ); + assertTrue( Hibernate.isInitialized( group.getLead() ) ); + assertFalse( Hibernate.isInitialized( group.getLead().getCollaborators() ) ); + assertTrue( Hibernate.isInitialized( group.getManager() ) ); + assertFalse( Hibernate.isInitialized( group.getManager().getCollaborators() ) ); // the collections should be uninitialized - assertFalse( Hibernate.isInitialized( groups[i].getEmployees() ) ); + assertFalse( Hibernate.isInitialized( group.getEmployees() ) ); } // both Group proxies should have been loaded in the same batch; assertEquals( 1, sessionFactory().getStatistics().getPrepareStatementCount() ); sessionFactory().getStatistics().clear(); - for (EmployeeGroup group : groups) { + for ( EmployeeGroup group : groups ) { assertTrue( Hibernate.isInitialized( group ) ); assertFalse( Hibernate.isInitialized( group.getEmployees() ) ); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/temporal/TimePropertyTest.java b/hibernate-core/src/test/java/org/hibernate/test/temporal/TimePropertyTest.java index 935d011c14..d77d122861 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/temporal/TimePropertyTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/temporal/TimePropertyTest.java @@ -9,6 +9,7 @@ package org.hibernate.test.temporal; import java.sql.Time; import java.text.DateFormat; import java.text.SimpleDateFormat; +import java.util.Calendar; import java.util.Date; import javax.persistence.GeneratedValue; import javax.persistence.Id; @@ -36,7 +37,11 @@ public class TimePropertyTest extends BaseCoreFunctionalTestCase { @Test public void testTimeAsDate() { final Entity eOrig = new Entity(); - eOrig.tAsDate = new Time( new Date().getTime() ); + Calendar calendar = Calendar.getInstance(); + // See javadoc for java.sql.Time: 'The date components should be set to the "zero epoch" value of January 1, 1970 and should not be accessed' + // Other dates can potentially lead to errors in JDBC drivers, in particular MySQL ConnectorJ 8.x. + calendar.set( 1970, Calendar.JANUARY, 1 ); + eOrig.tAsDate = new Time( calendar.getTimeInMillis() ); Session s = openSession(); s.getTransaction().begin(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/tm/AfterCompletionTest.java b/hibernate-core/src/test/java/org/hibernate/test/tm/AfterCompletionTest.java index ef0007f52e..b604577021 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/tm/AfterCompletionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/tm/AfterCompletionTest.java @@ -20,6 +20,7 @@ import org.hibernate.Session; import org.hibernate.action.spi.AfterTransactionCompletionProcess; import org.hibernate.action.spi.BeforeTransactionCompletionProcess; import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.spi.ActionQueue; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.junit.Test; @@ -71,8 +72,9 @@ public class AfterCompletionTest extends BaseNonConfigCoreFunctionalTestCase { // The before causes the original thread to wait until Reaper aborts the transaction // The after tracks whether it is invoked since this test is to guarantee it is called final SessionImplementor sessionImplementor = (SessionImplementor) session; - sessionImplementor.getActionQueue().registerProcess( new AfterCallbackCompletionHandler() ); - sessionImplementor.getActionQueue().registerProcess( new BeforeCallbackCompletionHandler() ); + final ActionQueue actionQueue = sessionImplementor.getActionQueue(); + actionQueue.registerProcess( new AfterCallbackCompletionHandler() ); + actionQueue.registerProcess( new BeforeCallbackCompletionHandler() ); TestingJtaPlatformImpl.transactionManager().commit(); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/tool/schema/SchemaGenetationSciptsActionPropertyValueEndingWithSpaceTest.java b/hibernate-core/src/test/java/org/hibernate/test/tool/schema/SchemaGenetationSciptsActionPropertyValueEndingWithSpaceTest.java new file mode 100644 index 0000000000..70b35aaacb --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/tool/schema/SchemaGenetationSciptsActionPropertyValueEndingWithSpaceTest.java @@ -0,0 +1,75 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.test.tool.schema; + +import java.io.File; +import java.io.IOException; +import java.util.Properties; + +import org.hibernate.boot.registry.BootstrapServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.boot.registry.internal.StandardServiceRegistryImpl; +import org.hibernate.cfg.Configuration; +import org.hibernate.internal.util.config.ConfigurationHelper; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +import static org.junit.Assert.fail; + +/** + * @author Andrea Boriero + */ +@TestForIssue(jiraKey = "HHH-13621") +public class SchemaGenetationSciptsActionPropertyValueEndingWithSpaceTest extends BaseCoreFunctionalTestCase { + + private File dropOutput; + private File createOutput; + + @Override + protected StandardServiceRegistryImpl buildServiceRegistry( + BootstrapServiceRegistry bootRegistry, + Configuration configuration) { + try { + dropOutput = File.createTempFile( "drop_script", ".sql" ); + createOutput = File.createTempFile( "create_script", ".sql" ); + dropOutput.deleteOnExit(); + createOutput.deleteOnExit(); + } + catch (IOException e) { + fail( "unable to create temp file" + e ); + } + Properties properties = new Properties(); + properties.putAll( configuration.getProperties() ); + // the value of the property ends with a space + properties.setProperty( "javax.persistence.schema-generation.scripts.action", "drop-and-create " ); + properties.setProperty( + "javax.persistence.schema-generation.scripts.create-target", + createOutput.getAbsolutePath() + ); + properties.setProperty( + "javax.persistence.schema-generation.scripts.drop-target", + dropOutput.getAbsolutePath() + ); + ConfigurationHelper.resolvePlaceHolders( properties ); + + StandardServiceRegistryBuilder cfgRegistryBuilder = configuration.getStandardServiceRegistryBuilder(); + + StandardServiceRegistryBuilder registryBuilder = new StandardServiceRegistryBuilder( + bootRegistry, + cfgRegistryBuilder.getAggregatedCfgXml() + ).applySettings( properties ); + + prepareBasicRegistryBuilder( registryBuilder ); + return (StandardServiceRegistryImpl) registryBuilder.build(); + } + + @Test + public void testValueEndingWithSpaceDoesNotCauseExceptionDuringBootstrap() { + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/AbstractJavaTimeTypeTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/AbstractJavaTimeTypeTest.java index 69384e1675..8baef27657 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/AbstractJavaTimeTypeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/AbstractJavaTimeTypeTest.java @@ -191,7 +191,7 @@ abstract class AbstractJavaTimeTypeTest extends BaseCoreFunctionalTestCase protected final void withDefaultTimeZone(Runnable runnable) { TimeZone timeZoneBefore = TimeZone.getDefault(); - TimeZone.setDefault( TimeZone.getTimeZone( env.defaultJvmTimeZone ) ); + TimeZone.setDefault( toTimeZone( env.defaultJvmTimeZone ) ); /* * Run the code in a new thread, because some libraries (looking at you, h2 JDBC driver) * cache data dependent on the default timezone in thread local variables, @@ -223,6 +223,23 @@ abstract class AbstractJavaTimeTypeTest extends BaseCoreFunctionalTestCase } } + private static TimeZone toTimeZone(ZoneId zoneId) { + String idString = zoneId.getId(); + if ( idString.startsWith( "UTC+" ) || idString.startsWith( "UTC-" ) ) { + // Apparently TimeZone doesn't understand UTC+XXX nor UTC-XXX + // Using GMT+XXX or GMT-XXX as a fallback + idString = "GMT" + idString.substring( "UTC".length() ); + } + + TimeZone result = TimeZone.getTimeZone( idString ); + if ( !idString.equals( result.getID() ) ) { + // If the timezone is not understood, getTimeZone returns GMT and the condition above is true + throw new IllegalStateException( "Attempting to test an unsupported timezone: " + zoneId ); + } + + return result; + } + protected final Class getRemappingDialectClass() { return env.remappingDialectClass; } diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/InstantTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/InstantTest.java index 2b960ae667..f1d159b8b0 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/InstantTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/InstantTest.java @@ -24,6 +24,7 @@ import javax.persistence.Id; import org.hibernate.dialect.MariaDBDialect; import org.hibernate.dialect.MySQLDialect; +import org.hibernate.dialect.SybaseDialect; import org.junit.runners.Parameterized; @@ -64,6 +65,11 @@ public class InstantTest extends AbstractJavaTimeTypeTest b .add( 1600, 1, 1, 0, 0, 0, 0, ZONE_AMSTERDAM ) ) // HHH-13379: DST end (where Timestamp becomes ambiguous, see JDK-4312621) diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTest.java index 50a00f62c2..5f370c8b02 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTest.java @@ -23,6 +23,7 @@ import javax.persistence.Id; import org.hibernate.dialect.AbstractHANADialect; import org.hibernate.dialect.MariaDBDialect; +import org.hibernate.dialect.MySQL5Dialect; import org.hibernate.dialect.MySQLDialect; import org.hibernate.type.descriptor.sql.TimestampTypeDescriptor; @@ -34,7 +35,13 @@ import org.junit.runners.Parameterized; * Tests for storage of LocalDate properties. */ @TestForIssue(jiraKey = "HHH-10371") -@SkipForDialect(value = AbstractHANADialect.class, comment = "HANA systematically returns the wrong date when the JVM default timezone is not UTC") +@SkipForDialect(value = AbstractHANADialect.class, + comment = "HANA systematically returns the wrong date when the JVM default timezone is not UTC") +@SkipForDialect(value = MySQL5Dialect.class, + comment = "HHH-13582: MySQL ConnectorJ 8.x returns the wrong date" + + " when the JVM default timezone is different from the server timezone:" + + " https://bugs.mysql.com/bug.php?id=91112" +) public class LocalDateTest extends AbstractJavaTimeTypeTest { private static class ParametersBuilder extends AbstractParametersBuilder { diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTimeTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTimeTest.java index d61cd590c4..03e3cf0c5c 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTimeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTimeTest.java @@ -21,6 +21,7 @@ import javax.persistence.Id; import org.hibernate.dialect.MariaDBDialect; import org.hibernate.dialect.MySQLDialect; +import org.hibernate.dialect.SybaseDialect; import org.junit.runners.Parameterized; @@ -59,6 +60,11 @@ public class LocalDateTimeTest extends AbstractJavaTimeTypeTest b .add( 1600, 1, 1, 0, 0, 0, 0, ZONE_AMSTERDAM ) ) // HHH-13379: DST end (where Timestamp becomes ambiguous, see JDK-4312621) diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/LocalTimeTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/LocalTimeTest.java index 9c80a3eba1..73a5ad6b70 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/LocalTimeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/LocalTimeTest.java @@ -15,6 +15,7 @@ import java.sql.Types; import java.time.LocalTime; import java.time.ZoneId; import java.util.Arrays; +import java.util.Collections; import java.util.List; import javax.persistence.Basic; import javax.persistence.Column; @@ -23,6 +24,7 @@ import javax.persistence.Id; import org.hibernate.dialect.AbstractHANADialect; import org.hibernate.dialect.MariaDBDialect; +import org.hibernate.dialect.MySQL5Dialect; import org.hibernate.dialect.MySQLDialect; import org.hibernate.type.descriptor.sql.TimestampTypeDescriptor; @@ -55,6 +57,18 @@ public class LocalTimeTest extends AbstractJavaTimeTypeTest getHibernateJdbcTimeZonesToTest() { + // The MariaDB Connector/J JDBC driver has a bug in ResultSet#getTime(int, Calendar) + // that prevents our explicit JDBC timezones from being recognized + // See https://hibernate.atlassian.net/browse/HHH-13581 + // See https://jira.mariadb.org/browse/CONJ-724 + if ( MariaDBDialect.class.isInstance( getDialect() ) ) { + return Collections.emptySet(); + } + return super.getHibernateJdbcTimeZonesToTest(); + } } @Parameterized.Parameters(name = "{1}:{2}:{3}.{4} (JDBC write date: {5}-{6}-{7}) {0}") @@ -175,12 +189,23 @@ public class LocalTimeTest extends AbstractJavaTimeTypeTest b .add( 1600, 1, 1, 0, 0, 0, 0, "+00:19:32", ZONE_AMSTERDAM ) ) // HHH-13379: DST end (where Timestamp becomes ambiguous, see JDK-4312621) diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/OffsetTimeTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/OffsetTimeTest.java index e7a2066f95..16bef9c237 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/OffsetTimeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/OffsetTimeTest.java @@ -17,6 +17,7 @@ import java.time.OffsetTime; import java.time.ZoneId; import java.time.ZoneOffset; import java.util.Arrays; +import java.util.Collections; import java.util.List; import javax.persistence.Basic; import javax.persistence.Column; @@ -25,6 +26,7 @@ import javax.persistence.Id; import org.hibernate.dialect.AbstractHANADialect; import org.hibernate.dialect.MariaDBDialect; +import org.hibernate.dialect.MySQL5Dialect; import org.hibernate.dialect.MySQLDialect; import org.hibernate.type.descriptor.sql.BigIntTypeDescriptor; import org.hibernate.type.descriptor.sql.TimestampTypeDescriptor; @@ -34,7 +36,7 @@ import org.junit.Test; import org.junit.runners.Parameterized; /** - * Tests for storage of LocalTime properties. + * Tests for storage of OffsetTime properties. */ public class OffsetTimeTest extends AbstractJavaTimeTypeTest { @@ -58,6 +60,18 @@ public class OffsetTimeTest extends AbstractJavaTimeTypeTest getHibernateJdbcTimeZonesToTest() { + // The MariaDB Connector/J JDBC driver has a bug in ResultSet#getTime(int, Calendar) + // that prevents our explicit JDBC timezones from being recognized + // See https://hibernate.atlassian.net/browse/HHH-13581 + // See https://jira.mariadb.org/browse/CONJ-724 + if ( MariaDBDialect.class.isInstance( getDialect() ) ) { + return Collections.emptySet(); + } + return super.getHibernateJdbcTimeZonesToTest(); + } } @Parameterized.Parameters(name = "{1}:{2}:{3}.{4}[{5}] (JDBC write date: {6}-{7}-{8}) {0}") @@ -204,12 +218,23 @@ public class OffsetTimeTest extends AbstractJavaTimeTypeTest b .add( 1600, 1, 1, 0, 0, 0, 0, "GMT+00:19:32", ZONE_AMSTERDAM ) .add( 1600, 1, 1, 0, 0, 0, 0, "Europe/Amsterdam", ZONE_AMSTERDAM ) ) diff --git a/hibernate-core/src/test/java/org/hibernate/test/util/StringHelperTest.java b/hibernate-core/src/test/java/org/hibernate/test/util/StringHelperTest.java index e5839966f6..899d871026 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/util/StringHelperTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/util/StringHelperTest.java @@ -108,7 +108,27 @@ public class StringHelperTest extends BaseUnitTestCase { } @Test - public void testCountUnquotedReturnsOne() { + public void replaceRepeatingPlaceholdersWithoutStackOverflow() { + String ordinalParameters = generateOrdinalParameters( 3, 19999 ); + String result = StringHelper.replace( + "select * from books where category in (?1) and id in(" + ordinalParameters + ") and parent_category in (?1) and id in(" + ordinalParameters + ")", + "?1", "?1, ?2", true, true ); + assertEquals( "select * from books where category in (?1, ?2) and id in(" + ordinalParameters + ") and parent_category in (?1, ?2) and id in(" + ordinalParameters + ")", result ); + } + + private String generateOrdinalParameters(int startPosition, int endPosition) { + StringBuilder builder = new StringBuilder(); + for ( int i = startPosition; i <= endPosition; i++ ) { + builder.append( '?' ).append( i ); + if ( i < endPosition ) { + builder.append( ", " ); + } + } + return builder.toString(); + } + + @Test + public void testCountUnquotedReturnsOne() { assertEquals(1, StringHelper.countUnquoted("1", '1')); assertEquals(0, StringHelper.countUnquoted("1", '\u0000')); assertEquals(0, StringHelper.countUnquoted("\'", '\u0000')); diff --git a/hibernate-core/src/test/resources/META-INF/services/org.hibernate.boot.registry.classloading.internal.MyService b/hibernate-core/src/test/resources/META-INF/services/org.hibernate.boot.registry.classloading.internal.MyService new file mode 100644 index 0000000000..89b4c390ee --- /dev/null +++ b/hibernate-core/src/test/resources/META-INF/services/org.hibernate.boot.registry.classloading.internal.MyService @@ -0,0 +1 @@ +org.hibernate.boot.registry.classloading.internal.MyServiceImpl \ No newline at end of file diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/mapper/id/EmbeddedIdMapper.java b/hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/mapper/id/EmbeddedIdMapper.java index ed62002ccd..abbc84872a 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/mapper/id/EmbeddedIdMapper.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/internal/entities/mapper/id/EmbeddedIdMapper.java @@ -16,7 +16,6 @@ import java.util.Map; import org.hibernate.envers.exception.AuditException; import org.hibernate.envers.internal.entities.PropertyData; import org.hibernate.envers.internal.tools.ReflectionTools; -import org.hibernate.internal.util.ReflectHelper; import org.hibernate.property.access.spi.Getter; import org.hibernate.property.access.spi.Setter; import org.hibernate.service.ServiceRegistry; @@ -73,11 +72,10 @@ public class EmbeddedIdMapper extends AbstractCompositeIdMapper implements Simpl new PrivilegedAction() { @Override public Boolean run() { - final Getter getter = ReflectionTools.getGetter( obj.getClass(), idPropertyData, getServiceRegistry() ); final Setter setter = ReflectionTools.getSetter( obj.getClass(), idPropertyData, getServiceRegistry() ); try { - final Object subObj = ReflectHelper.getDefaultConstructor( getter.getReturnType() ).newInstance(); + final Object subObj = instantiateCompositeId(); boolean ret = true; for ( IdMapper idMapper : ids.values() ) { diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/ids/embeddedid/EmbeddedIdGenericsTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/ids/embeddedid/EmbeddedIdGenericsTest.java new file mode 100644 index 0000000000..67de748ab9 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/ids/embeddedid/EmbeddedIdGenericsTest.java @@ -0,0 +1,221 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.envers.test.integration.ids.embeddedid; + +import java.io.Serializable; +import java.util.List; +import java.util.Objects; + +import javax.persistence.Column; +import javax.persistence.Embeddable; +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.MappedSuperclass; +import javax.persistence.Table; + +import org.hibernate.envers.Audited; +import org.hibernate.envers.RelationTargetAuditMode; +import org.hibernate.envers.test.BaseEnversJPAFunctionalTestCase; +import org.hibernate.envers.test.Priority; +import org.junit.Test; + +import org.hibernate.testing.TestForIssue; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +/** + * Tests an entity mapping that uses an {@link EmbeddedId} mapping that makes use of generics. + * + * @author Chris Cranford + */ +@TestForIssue(jiraKey = "HHH-13564") +public class EmbeddedIdGenericsTest extends BaseEnversJPAFunctionalTestCase { + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { NotificationType.class, Trigger.class }; + } + + @Test + @Priority(10) + public void initData() { + // Revision 1 + // Store NotificationType and Trigger instance + doInJPA( this::entityManagerFactory, entityManager -> { + final NotificationType type = new NotificationType( "code" ); + entityManager.persist( type ); + + Trigger trigger = new Trigger( "str", type ); + entityManager.persist( trigger ); + + trigger.setActive( !trigger.isActive() ); + entityManager.merge( trigger ); + } ); + } + + @Test + public void testAuditQueryMappedSuperclassWithEmbeddedId() { + // There should be at least one revision for Trigger + List resultList = getAuditReader().createQuery().forRevisionsOfEntity( Trigger.class, true, true ).getResultList(); + assertEquals( 1, resultList.size() ); + + // Trigger should be hydrated with a composite-id values below + Trigger entityInstance = (Trigger) resultList.get( 0 ); + assertEquals( "str", entityInstance.getPk().getEventType() ); + assertEquals( "code", entityInstance.getPk().getNotificationType().getCode() ); + } + + + @MappedSuperclass + public abstract static class CompositeIdBaseEntity implements Serializable { + protected PK pk; + + @EmbeddedId + public PK getPk() { + return pk; + } + + public void setPk(PK pk) { + this.pk = pk; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + CompositeIdBaseEntity that = (CompositeIdBaseEntity) o; + return Objects.equals( pk, that.pk ); + } + + @Override + public int hashCode() { + return Objects.hash( pk ); + } + } + + @Audited + @Entity(name = "Trigger") + @Table(name = "`Trigger`") + public static class Trigger extends CompositeIdBaseEntity { + private boolean active; + + Trigger() { + + } + + public Trigger(String eventType, NotificationType notificationType) { + this.pk = new TriggerPK( eventType, notificationType ); + } + + public boolean isActive() { + return active; + } + + public void setActive(boolean active) { + this.active = active; + } + + @Embeddable + public static class TriggerPK implements Serializable { + private String eventType; + private NotificationType notificationType; + + TriggerPK() { + + } + + public TriggerPK(String eventType, NotificationType notificationType) { + this.eventType = eventType; + this.notificationType = notificationType; + } + + @Column(nullable = false, insertable = false, updatable = false) + public String getEventType() { + return eventType; + } + + public void setEventType(String eventType) { + this.eventType = eventType; + } + + @Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED) + @ManyToOne + @JoinColumn(insertable = false, updatable = false, nullable = false) + public NotificationType getNotificationType() { + return notificationType; + } + + public void setNotificationType(NotificationType notificationType) { + this.notificationType = notificationType; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + TriggerPK triggerPK = (TriggerPK) o; + return Objects.equals( eventType, triggerPK.eventType ) && + Objects.equals( notificationType, triggerPK.notificationType ); + } + + @Override + public int hashCode() { + return Objects.hash( eventType, notificationType ); + } + } + } + + @Entity(name = "NotificationType") + public static class NotificationType { + @Id + private String code; + + public NotificationType() { + + } + + public NotificationType(String code) { + this.code = code; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + NotificationType that = (NotificationType) o; + return Objects.equals( code, that.code ); + } + + @Override + public int hashCode() { + return Objects.hash( code ); + } + } +} diff --git a/hibernate-jipijapa/src/main/java/org/jboss/as/jpa/hibernate5/HibernatePersistenceProviderAdaptor.java b/hibernate-jipijapa/src/main/java/org/jboss/as/jpa/hibernate5/HibernatePersistenceProviderAdaptor.java index 10d0e5a438..d8a330d830 100644 --- a/hibernate-jipijapa/src/main/java/org/jboss/as/jpa/hibernate5/HibernatePersistenceProviderAdaptor.java +++ b/hibernate-jipijapa/src/main/java/org/jboss/as/jpa/hibernate5/HibernatePersistenceProviderAdaptor.java @@ -108,7 +108,7 @@ public class HibernatePersistenceProviderAdaptor implements PersistenceProviderA if ( Classification.NONE.equals( platform.defaultCacheClassification() ) ) { if ( !SharedCacheMode.NONE.equals( pu.getSharedCacheMode() ) ) { - JPA_LOGGER.tracef( "second level cache is not supported in platform, ignoring shared cache mode" ); + JPA_LOGGER.trace( "second level cache is not supported in platform, ignoring shared cache mode" ); } pu.setSharedCacheMode( SharedCacheMode.NONE ); } @@ -127,13 +127,15 @@ public class HibernatePersistenceProviderAdaptor implements PersistenceProviderA JPA_LOGGER.tracef( "second level cache enabled for %s", pu.getScopedPersistenceUnitName() ); } else { - JPA_LOGGER.tracef( - "second level cache disabled for %s, pu %s property = %s, pu.getSharedCacheMode = %s", - pu.getScopedPersistenceUnitName(), - AvailableSettings.JPA_SHARED_CACHE_MODE, - sharedCacheMode, - pu.getSharedCacheMode().toString() - ); + if ( JPA_LOGGER.isTraceEnabled() ) { + JPA_LOGGER.tracef( + "second level cache disabled for %s, pu %s property = %s, pu.getSharedCacheMode = %s", + pu.getScopedPersistenceUnitName(), + AvailableSettings.JPA_SHARED_CACHE_MODE, + sharedCacheMode, + pu.getSharedCacheMode().toString() + ); + } } } diff --git a/hibernate-orm-modules/hibernate-orm-modules.gradle b/hibernate-orm-modules/hibernate-orm-modules.gradle index 4d78ba2992..b4f865936d 100644 --- a/hibernate-orm-modules/hibernate-orm-modules.gradle +++ b/hibernate-orm-modules/hibernate-orm-modules.gradle @@ -39,11 +39,6 @@ ext { fpackStagingDir = file( "target/featurepack" ) //Target build directory for the Feature Pack } -if ( JavaVersion.current().isJava11Compatible() ) { - logger.warn( '[WARN] Skipping all tests for hibernate-orm-modules due to Gradle issues with JDK 11' ) - test.enabled = false -} - description = "Feature Pack of Hibernate ORM modules for WildFly ${project.wildFlyMajorVersion}" configurations { @@ -144,6 +139,10 @@ task prepareWildFlyForTests( dependsOn: [provision] ) { description = 'Downloads the WildFly distribution, installs it into a local directory, includes present version of Hibernate ORM, JPA 2.2 : ready for integration tests' } +test { + exclude 'org/hibernate/wildfly/model/**' +} + test.dependsOn prepareWildFlyForTests diff --git a/hibernate-orm-modules/src/test/java/org/hibernate/wildfly/integrationtest/HibernateEnversOnWildflyTest.java b/hibernate-orm-modules/src/test/java/org/hibernate/wildfly/integrationtest/HibernateEnversOnWildflyTest.java index 05178337d4..b1f995cfce 100644 --- a/hibernate-orm-modules/src/test/java/org/hibernate/wildfly/integrationtest/HibernateEnversOnWildflyTest.java +++ b/hibernate-orm-modules/src/test/java/org/hibernate/wildfly/integrationtest/HibernateEnversOnWildflyTest.java @@ -16,6 +16,8 @@ import javax.transaction.UserTransaction; import org.hibernate.Session; import org.hibernate.envers.AuditReader; import org.hibernate.envers.AuditReaderFactory; +import org.hibernate.wildfly.model.AuditedEntity; + import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.junit.Arquillian; import org.jboss.shrinkwrap.api.ShrinkWrap; diff --git a/hibernate-orm-modules/src/test/java/org/hibernate/wildfly/integrationtest/HibernateModulesOnWildflyTest.java b/hibernate-orm-modules/src/test/java/org/hibernate/wildfly/integrationtest/HibernateModulesOnWildflyTest.java index f35dd758ed..99ff429b88 100644 --- a/hibernate-orm-modules/src/test/java/org/hibernate/wildfly/integrationtest/HibernateModulesOnWildflyTest.java +++ b/hibernate-orm-modules/src/test/java/org/hibernate/wildfly/integrationtest/HibernateModulesOnWildflyTest.java @@ -13,6 +13,8 @@ import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import org.hibernate.Session; +import org.hibernate.wildfly.model.Kryptonite; + import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.junit.Arquillian; import org.jboss.shrinkwrap.api.ShrinkWrap; diff --git a/hibernate-orm-modules/src/test/java/org/hibernate/wildfly/integrationtest/TransactionRollbackTest.java b/hibernate-orm-modules/src/test/java/org/hibernate/wildfly/integrationtest/TransactionRollbackTest.java index dd57243050..6f4123451d 100644 --- a/hibernate-orm-modules/src/test/java/org/hibernate/wildfly/integrationtest/TransactionRollbackTest.java +++ b/hibernate-orm-modules/src/test/java/org/hibernate/wildfly/integrationtest/TransactionRollbackTest.java @@ -12,6 +12,7 @@ import javax.persistence.PersistenceContext; import org.hibernate.Session; import org.hibernate.engine.transaction.spi.TransactionImplementor; +import org.hibernate.wildfly.model.Kryptonite; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/hibernate-orm-modules/src/test/java/org/hibernate/wildfly/integrationtest/AuditedEntity.java b/hibernate-orm-modules/src/test/java/org/hibernate/wildfly/model/AuditedEntity.java similarity index 96% rename from hibernate-orm-modules/src/test/java/org/hibernate/wildfly/integrationtest/AuditedEntity.java rename to hibernate-orm-modules/src/test/java/org/hibernate/wildfly/model/AuditedEntity.java index 00a974b7c8..0d6f48950a 100644 --- a/hibernate-orm-modules/src/test/java/org/hibernate/wildfly/integrationtest/AuditedEntity.java +++ b/hibernate-orm-modules/src/test/java/org/hibernate/wildfly/model/AuditedEntity.java @@ -4,7 +4,7 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later. * See the lgpl.txt file in the root directory or . */ -package org.hibernate.wildfly.integrationtest; +package org.hibernate.wildfly.model; import javax.persistence.Entity; import javax.persistence.Id; diff --git a/hibernate-orm-modules/src/test/java/org/hibernate/wildfly/integrationtest/Kryptonite.java b/hibernate-orm-modules/src/test/java/org/hibernate/wildfly/model/Kryptonite.java similarity index 90% rename from hibernate-orm-modules/src/test/java/org/hibernate/wildfly/integrationtest/Kryptonite.java rename to hibernate-orm-modules/src/test/java/org/hibernate/wildfly/model/Kryptonite.java index 7b073fe6f3..3ab61e224b 100644 --- a/hibernate-orm-modules/src/test/java/org/hibernate/wildfly/integrationtest/Kryptonite.java +++ b/hibernate-orm-modules/src/test/java/org/hibernate/wildfly/model/Kryptonite.java @@ -4,7 +4,7 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later. * See the lgpl.txt file in the root directory or . */ -package org.hibernate.wildfly.integrationtest; +package org.hibernate.wildfly.model; import javax.persistence.Entity; import javax.persistence.Id; diff --git a/hibernate-spatial/databases/mysql56/matrix.gradle b/hibernate-spatial/databases/mysql56/matrix.gradle index 68f6ea4f81..361a524b53 100644 --- a/hibernate-spatial/databases/mysql56/matrix.gradle +++ b/hibernate-spatial/databases/mysql56/matrix.gradle @@ -4,4 +4,4 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later. * See the lgpl.txt file in the root directory or . */ -jdbcDependency "mysql:mysql-connector-java:5.1.15" +jdbcDependency "mysql:mysql-connector-java:8.0.17" diff --git a/hibernate-spatial/databases/mysql8/matrix.gradle b/hibernate-spatial/databases/mysql8/matrix.gradle index 8ddca02827..361a524b53 100644 --- a/hibernate-spatial/databases/mysql8/matrix.gradle +++ b/hibernate-spatial/databases/mysql8/matrix.gradle @@ -4,4 +4,4 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later. * See the lgpl.txt file in the root directory or . */ -jdbcDependency "mysql:mysql-connector-java:8.0.12" +jdbcDependency "mysql:mysql-connector-java:8.0.17" diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit4/ExtraAssertions.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit4/ExtraAssertions.java index 491356db20..d8bbf697c5 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/junit4/ExtraAssertions.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit4/ExtraAssertions.java @@ -73,8 +73,7 @@ public final class ExtraAssertions { private static Map generateJdbcTypeCache() { final Field[] fields = Types.class.getFields(); Map cache = new HashMap( (int)( fields.length * .75 ) + 1 ); - for ( int i = 0; i < fields.length; i++ ) { - final Field field = fields[i]; + for ( Field field : fields ) { if ( Modifier.isStatic( field.getModifiers() ) ) { try { cache.put( field.get( null ), field.getName() ); diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/transaction/TransactionUtil.java b/hibernate-testing/src/main/java/org/hibernate/testing/transaction/TransactionUtil.java index 95e2ce9e5b..67de5eb55c 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/transaction/TransactionUtil.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/transaction/TransactionUtil.java @@ -32,6 +32,7 @@ import org.hibernate.dialect.H2Dialect; import org.hibernate.dialect.MySQLDialect; import org.hibernate.dialect.PostgreSQL81Dialect; import org.hibernate.dialect.SQLServerDialect; +import org.hibernate.dialect.SybaseASE15Dialect; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.jboss.logging.Logger; @@ -573,6 +574,12 @@ public class TransactionUtil { st.execute(String.format( "SET TRANSACTION LOCK WAIT TIMEOUT %d", millis )); } } + else if( Dialect.getDialect() instanceof SybaseASE15Dialect) { + try (Statement st = connection.createStatement()) { + //Prepared Statements fail for SET commands + st.execute(String.format( "SET LOCK WAIT %d", millis/1000 )); + } + } else { try { connection.setNetworkTimeout( Executors.newSingleThreadExecutor(), (int) millis ); diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/util/ExceptionUtil.java b/hibernate-testing/src/main/java/org/hibernate/testing/util/ExceptionUtil.java index cb244aecea..c2ef8bef79 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/util/ExceptionUtil.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/util/ExceptionUtil.java @@ -79,11 +79,13 @@ public class ExceptionUtil { else { Throwable rootCause = ExceptionUtil.rootCause( e ); if ( - rootCause != null && ( - rootCause.getMessage().contains( "timeout" ) || - rootCause.getMessage().contains( "timed out" ) || - rootCause.getMessage().contains( "lock(s) could not be acquired" ) - ) + rootCause != null && ( + rootCause.getMessage().contains( "timeout" ) || + rootCause.getMessage().contains( "timed out" ) || + rootCause.getMessage().contains( "lock(s) could not be acquired" ) || + rootCause.getMessage().contains( "Could not acquire a lock" ) + + ) ) { return true; }