From d4e1b7ffbcb5f92901b57cbb5d288915a4af5c7c Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Sun, 11 Aug 2019 21:18:34 +0100 Subject: [PATCH 01/99] HHH-13561 Do not retrieve the same ActionQueue multiple times --- .../internal/DefaultDirtyCheckEventListener.java | 8 +++++--- .../event/internal/DefaultRefreshEventListener.java | 12 +++++++++--- .../java/org/hibernate/internal/SessionImpl.java | 3 ++- .../org/hibernate/test/tm/AfterCompletionTest.java | 6 ++++-- 4 files changed, 20 insertions(+), 9 deletions(-) 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 5e42eb1cbcba..7f28bb1b7a15 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 void onDirtyCheck(DirtyCheckEvent event) throws HibernateException { event.setDirty( wasNeeded ); } finally { - event.getSession().getActionQueue().clearFromFlushNeededCheck( oldSize ); + actionQueue.clearFromFlushNeededCheck( oldSize ); } } } 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 0c0f2624fbbe..9b8b9c41b652 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.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 @@ private void evictCachedCollections(EntityPersister persister, Serializable id, 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/internal/SessionImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java index 48831601534d..0589a070dcbd 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -422,7 +422,8 @@ protected boolean shouldCloseJdbcCoordinatorOnClose(boolean isTransactionCoordin 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" ); 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 ef0007f52e90..b60457702118 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.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 void testAfterCompletionCallbackExecutedAfterTransactionTimeout() throws // 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(); } From 25ca80b1c5a58862e94d0bc5679f2ca6c39617af Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Sun, 11 Aug 2019 21:38:17 +0100 Subject: [PATCH 02/99] HHH-13563 ResultSetReturnImpl is looking up JdbcServices on each construction --- .../engine/jdbc/internal/JdbcCoordinatorImpl.java | 2 +- .../engine/jdbc/internal/ResultSetReturnImpl.java | 9 +-------- 2 files changed, 2 insertions(+), 9 deletions(-) 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 9a4ba49a3bf5..0d5dd3876b95 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 @@ -223,7 +223,7 @@ public StatementPreparer getStatementPreparer() { @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 63298d552211..c39549eced8e 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(); } From 3e17be98324a2aed8d18aab573c11a846e40c106 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Sun, 11 Aug 2019 21:50:15 +0100 Subject: [PATCH 03/99] HHH-13562 List of TransactionObserver for JdbcResourceLocalTransactionCoordinatorImpl should be lazily initialized --- ...esourceLocalTransactionCoordinatorImpl.java | 18 ++++++++++++++---- .../SessionWithSharedConnectionTest.java | 4 +++- 2 files changed, 17 insertions(+), 5 deletions(-) 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 1bbde9357982..a637c01c19c9 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 @@ private void afterCompletionCallback(boolean successful) { @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/test/java/org/hibernate/sharedSession/SessionWithSharedConnectionTest.java b/hibernate-core/src/test/java/org/hibernate/sharedSession/SessionWithSharedConnectionTest.java index 703174f6f801..ebec824f328b 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 void testSharedSessionTransactionObserver() throws Exception { } 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; From 1b06b76e17a41b977a43c48551c3a844704044e7 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Mon, 12 Aug 2019 19:38:09 -0700 Subject: [PATCH 04/99] HHH-13557 : LocalTimeTest#writeThenNativeRead and OffsetTimeTest#writeThenNativeRead tests are failing on SQL Server --- .../test/java/org/hibernate/test/type/LocalTimeTest.java | 7 ++++++- .../test/java/org/hibernate/test/type/OffsetTimeTest.java | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) 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 9c80a3eba1b3..e4139ed48c84 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 @@ -175,7 +175,12 @@ protected Object getExpectedJdbcValueAfterHibernateWrite() { @Override protected Object getActualJdbcValue(ResultSet resultSet, int columnIndex) throws SQLException { - return resultSet.getTimestamp( columnIndex ); + if ( TimeAsTimestampRemappingH2Dialect.class.equals( getRemappingDialectClass() ) ) { + return resultSet.getTimestamp( columnIndex ); + } + else { + return resultSet.getTime( columnIndex ); + } } @Override 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 e7a2066f95a4..1d12f7f9ff5d 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 @@ -204,7 +204,12 @@ protected Object getExpectedJdbcValueAfterHibernateWrite() { @Override protected Object getActualJdbcValue(ResultSet resultSet, int columnIndex) throws SQLException { - return resultSet.getTimestamp( columnIndex ); + if ( TimeAsTimestampRemappingH2Dialect.class.equals( getRemappingDialectClass() ) ) { + return resultSet.getTimestamp( columnIndex ); + } + else { + return resultSet.getTime( columnIndex ); + } } @Override From 358307461933685fe1e6796b2d9165456ae3fffb Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Mon, 12 Aug 2019 21:30:20 -0700 Subject: [PATCH 05/99] HHH-13558 : InstantTest, LocalDateTimeTest, OffsetDateTimeTest, ZonedDateTimeTest failing on Sybase for year 1600 --- .../src/test/java/org/hibernate/test/type/InstantTest.java | 6 ++++++ .../java/org/hibernate/test/type/LocalDateTimeTest.java | 6 ++++++ .../java/org/hibernate/test/type/OffsetDateTimeTest.java | 6 ++++++ .../java/org/hibernate/test/type/ZonedDateTimeTest.java | 6 ++++++ 4 files changed, 24 insertions(+) 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 2b960ae66739..f1d159b8b0aa 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 org.hibernate.dialect.MariaDBDialect; import org.hibernate.dialect.MySQLDialect; +import org.hibernate.dialect.SybaseDialect; import org.junit.runners.Parameterized; @@ -64,6 +65,11 @@ public static List data() { .add( 1892, 1, 1, 0, 0, 0, 0, ZONE_OSLO ) .add( 1899, 12, 31, 23, 59, 59, 999_999_999, ZONE_PARIS ) .add( 1899, 12, 31, 23, 59, 59, 999_999_999, ZONE_AMSTERDAM ) + ) + .skippedForDialects( + // MySQL/Mariadb/Sybase cannot store dates in 1600 in a timestamp. + Arrays.asList( MySQLDialect.class, MariaDBDialect.class, SybaseDialect.class ), + b -> 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/LocalDateTimeTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/LocalDateTimeTest.java index d61cd590c455..03e3cf0c5ca1 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 org.hibernate.dialect.MariaDBDialect; import org.hibernate.dialect.MySQLDialect; +import org.hibernate.dialect.SybaseDialect; import org.junit.runners.Parameterized; @@ -59,6 +60,11 @@ public static List data() { .add( 1892, 1, 1, 0, 0, 0, 0, ZONE_OSLO ) .add( 1900, 1, 1, 0, 9, 20, 0, ZONE_PARIS ) .add( 1900, 1, 1, 0, 19, 31, 0, ZONE_AMSTERDAM ) + ) + .skippedForDialects( + // MySQL/Mariadb/Sybase cannot store dates in 1600 in a timestamp. + Arrays.asList( MySQLDialect.class, MariaDBDialect.class, SybaseDialect.class ), + b -> 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/OffsetDateTimeTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/OffsetDateTimeTest.java index dab40cdc92c5..fa14923a5e64 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/OffsetDateTimeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/OffsetDateTimeTest.java @@ -25,6 +25,7 @@ import org.hibernate.Query; import org.hibernate.dialect.MariaDBDialect; import org.hibernate.dialect.MySQLDialect; +import org.hibernate.dialect.SybaseDialect; import org.hibernate.type.OffsetDateTimeType; import org.hibernate.testing.TestForIssue; @@ -91,6 +92,11 @@ public static List data() { .add( 1900, 1, 1, 0, 9, 20, 0, "+00:09:21", ZONE_PARIS ) .add( 1900, 1, 1, 0, 19, 31, 0, "+00:19:32", ZONE_PARIS ) .add( 1900, 1, 1, 0, 19, 31, 0, "+00:19:32", ZONE_AMSTERDAM ) + ) + .skippedForDialects( + // MySQL/Mariadb/Sybase cannot store dates in 1600 in a timestamp. + Arrays.asList( MySQLDialect.class, MariaDBDialect.class, SybaseDialect.class ), + b -> 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/ZonedDateTimeTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/ZonedDateTimeTest.java index 61dad123e915..b7c7201575bd 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/ZonedDateTimeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/ZonedDateTimeTest.java @@ -25,6 +25,7 @@ import org.hibernate.Query; import org.hibernate.dialect.MariaDBDialect; import org.hibernate.dialect.MySQLDialect; +import org.hibernate.dialect.SybaseDialect; import org.hibernate.type.ZonedDateTimeType; import org.hibernate.testing.TestForIssue; @@ -102,6 +103,11 @@ public static List data() { .add( 1900, 1, 1, 0, 19, 31, 0, "GMT+00:19:32", ZONE_PARIS ) .add( 1900, 1, 1, 0, 19, 31, 0, "GMT+00:19:32", ZONE_AMSTERDAM ) .add( 1900, 1, 1, 0, 19, 31, 0, "Europe/Amsterdam", ZONE_AMSTERDAM ) + ) + .skippedForDialects( + // MySQL/Mariadb/Sybase cannot store dates in 1600 in a timestamp. + Arrays.asList( MySQLDialect.class, MariaDBDialect.class, SybaseDialect.class ), + b -> 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 ) ) From 31fb14e0d9de1df1aa239e85542df54d992248bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Fri, 9 Aug 2019 19:02:03 +0200 Subject: [PATCH 06/99] HHH-13551 Restucture ClassPathAndModulePathAggregatedServiceLoader This does not change the behavior of the class at all: it simply restructures the code to allow for the changes in the next commits. --- .../internal/AggregatedServiceLoader.java | 104 ++++++++++-------- 1 file changed, 57 insertions(+), 47 deletions(-) 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 eead6d561a47..edc8907c5619 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 @@ -145,17 +145,14 @@ public void close() { * @param The type of loaded services. */ private static class ClassPathAndModulePathAggregatedServiceLoader extends AggregatedServiceLoader { + private final ServiceLoader aggregatedClassLoaderServiceLoader; private final List> delegates; private Collection cache = null; private ClassPathAndModulePathAggregatedServiceLoader(AggregatedClassLoader aggregatedClassLoader, Class 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 +182,67 @@ public Collection getAll() { 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 + providerStream( aggregatedClassLoaderServiceLoader ) + .forEach( provider -> 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 ) { + providerStream( delegate ) + .forEach( provider -> 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 ); + } + } + + /* + * 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; From 5174fc28dc2d4b13992af184179589f71083f4ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Fri, 9 Aug 2019 19:15:04 +0200 Subject: [PATCH 07/99] HHH-13551 Ignore ServiceConfigurationError thrown when accessing services of individual (non-aggregated) class loaders --- .../internal/AggregatedServiceLoader.java | 42 +++++++++++++++++-- .../hibernate/internal/CoreMessageLogger.java | 5 +++ 2 files changed, 43 insertions(+), 4 deletions(-) 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 edc8907c5619..6d902d046d96 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.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 @@ */ 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,12 +150,14 @@ public void close() { * @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<>(); this.aggregatedClassLoaderServiceLoader = ServiceLoader.load( serviceContract, aggregatedClassLoader ); final Iterator clIterator = aggregatedClassLoader.newClassLoaderIterator(); @@ -187,16 +194,32 @@ private Collection loadAll() { Set result = new LinkedHashSet<>(); // Always try the aggregated class loader first - providerStream( aggregatedClassLoaderServiceLoader ) - .forEach( provider -> collectServiceIfNotDuplicate( result, alreadyEncountered, provider ) ); + 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 ) { - providerStream( delegate ) - .forEach( provider -> collectServiceIfNotDuplicate( result, alreadyEncountered, provider ) ); + 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; @@ -212,6 +235,17 @@ private Stream> providerStream(ServiceLoader serviceLoa } } + 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 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 99d444745159..55e392a429a3 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.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 @@ void attemptToAssociateProxyWithTwoOpenSessions( @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); + } From f2f788c03d2b5e95c46376d9300297439742b6d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Fri, 9 Aug 2019 17:42:18 +0200 Subject: [PATCH 08/99] HHH-13551 Test the retrieval of a service when an "incompatible" classloader is provided --- .../internal/ClassLoaderServiceImplTest.java | 25 +++++++ .../internal/IsolatedClassLoader.java | 68 +++++++++++++++++++ .../classloading/internal/MyService.java | 12 ++++ .../classloading/internal/MyServiceImpl.java | 10 +++ ...t.registry.classloading.internal.MyService | 1 + 5 files changed, 116 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/boot/registry/classloading/internal/IsolatedClassLoader.java create mode 100644 hibernate-core/src/test/java/org/hibernate/boot/registry/classloading/internal/MyService.java create mode 100644 hibernate-core/src/test/java/org/hibernate/boot/registry/classloading/internal/MyServiceImpl.java create mode 100644 hibernate-core/src/test/resources/META-INF/services/org.hibernate.boot.registry.classloading.internal.MyService 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 78a7614108b1..14dfa58064f4 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 @@ 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 void testLookupNever() { 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 000000000000..a1a1a6d5c9ce --- /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 000000000000..ce0898803d9f --- /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 000000000000..fa9f185022b1 --- /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/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 000000000000..89b4c390eeb3 --- /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 From 2aee5a930d06ba10c79e08be5c37488ae5072a7c Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Tue, 13 Aug 2019 21:01:12 +0100 Subject: [PATCH 09/99] HHH-13556 Tests doing dynamic fetch scrolling a collection fail on DB2 --- .../lazy/StatelessQueryScrollingTest.java | 13 ++++++++++++- .../fetching/StatelessSessionFetchingTest.java | 14 +++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) 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 1bc6da1809eb..2aa0d1cdf448 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.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 void testDynamicFetchCollectionScroll() { 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/stateless/fetching/StatelessSessionFetchingTest.java b/hibernate-core/src/test/java/org/hibernate/test/stateless/fetching/StatelessSessionFetchingTest.java index 01f7d0e75f98..1b058e74e50d 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.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 void testDynamicFetchCollectionScroll() { 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 ) ); From 1a5b401d09e6ede20152e5d5855ab8fa1b9e9163 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Tue, 13 Aug 2019 16:08:36 +0100 Subject: [PATCH 10/99] HHH-13554 QueryAndSQLTest.testNativeQueryWithFormulaAttributeWithoutAlias() fails on MariaDB --- .../org/hibernate/test/annotations/query/QueryAndSQLTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 eea98617b988..59089a6a31a9 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 void testNativeQueryWithFormulaAttributeWithoutAlias() { 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(); From 937d4a35031cd7ee4968bec26b09de5815c4395e Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Tue, 13 Aug 2019 18:02:32 -0700 Subject: [PATCH 11/99] HHH-13569 : org.hibernate.test.annotations.embedded.EmbeddedTest failures on Sybase --- .../test/annotations/embedded/EmbeddedTest.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) 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 83cdea6f8e77..7b4427266106 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.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.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 @@ * @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 void testQueryWithEmbeddedParameterAllNull() throws Exception { @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(); From 9988d677b955154d188671cc0086153444b3e70c Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Tue, 13 Aug 2019 23:16:45 -0700 Subject: [PATCH 12/99] HHH-13571 : Test failures due to cross joined table out of scope of a subsequent JOIN on Sybase --- .../src/test/java/org/hibernate/test/hql/EntityJoinTest.java | 3 +++ 1 file changed, 3 insertions(+) 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 e140b5b091e3..7efff1e585fb 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.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 void testLeftOuterEntityJoins() { @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 From ce03ef96b9e46a673f7f6f345e8eaecb8ff3f309 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Tue, 13 Aug 2019 23:37:47 -0700 Subject: [PATCH 13/99] HHH-13570 : Test failures due to Sybase not supporting UPDATE statement with WITH(NOWAIT) --- .../test/java/org/hibernate/test/locking/LockModeTest.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/hibernate-core/src/test/java/org/hibernate/test/locking/LockModeTest.java b/hibernate-core/src/test/java/org/hibernate/test/locking/LockModeTest.java index 6dd9305a2d46..707f52125e56 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/locking/LockModeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/locking/LockModeTest.java @@ -19,6 +19,8 @@ import org.hibernate.dialect.SybaseDialect; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; @@ -60,6 +62,7 @@ public void prepareTest() throws Exception { protected boolean isCleanupTestDataRequired(){return true;} @Test + @RequiresDialectFeature( value = DialectChecks.SupportsLockTimeouts.class ) @SuppressWarnings( {"deprecation"}) public void testLoading() { // open a session, begin a transaction and lock row @@ -75,6 +78,7 @@ public void testLoading() { } @Test + @RequiresDialectFeature( value = DialectChecks.SupportsLockTimeouts.class ) public void testLegacyCriteria() { // open a session, begin a transaction and lock row doInHibernate( this::sessionFactory, session -> { @@ -92,6 +96,7 @@ public void testLegacyCriteria() { } @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 void testLegacyCriteriaAliasSpecific() { } @Test + @RequiresDialectFeature( value = DialectChecks.SupportsLockTimeouts.class ) public void testQuery() { // open a session, begin a transaction and lock row doInHibernate( this::sessionFactory, session -> { From fed93b0ae96bd39367897cbbec66327ddd83962d Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Wed, 14 Aug 2019 00:25:49 -0700 Subject: [PATCH 14/99] HHH-13573 : Test failure due to Sybase not supporting cascade delete on foreign key definitions --- .../hibernate/test/annotations/onetomany/OneToManyTest.java | 3 +++ 1 file changed, 3 insertions(+) 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 df06757ac22c..0027981c70c4 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.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 void testCascadeDelete() throws Exception { } @Test + @RequiresDialectFeature(DialectChecks.SupportsCascadeDeleteCheck.class) public void testCascadeDeleteWithUnidirectionalAssociation() throws Exception { OnDeleteUnidirectionalOneToManyChild child = new OnDeleteUnidirectionalOneToManyChild(); From 35037dac7b6e080c7d1dae31a64d0d5012b1b40c Mon Sep 17 00:00:00 2001 From: Legohuman Date: Sat, 10 Aug 2019 00:33:24 +0200 Subject: [PATCH 15/99] HHH-13259 Fix StackOverflowError in StringHelper Before fix method org.hibernate.internal.util.StringHelper#replace matched placeholders illegally in case when ordinal parameters list was expanded. Ex. placeholder ?1 was matched with ?11, ?12, ?13 etc. For queries with 2 or more IN clauses with different collections there were a situation when ?1 from the first clause matched with already expanded placeholders from the second collection. Each match led to recursive call of replace method. If collection in second clause was very long then StackOverflowError occurred. Fix adds check of partial placeholder match for wholeWords mode which is used in expanding list parameters. Partial matches are skipped during replace. --- .../hibernate/internal/util/StringHelper.java | 20 ++++++++++++++++++- .../hibernate/test/util/StringHelperTest.java | 20 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) 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 9c658ec37fc3..f79b03a65193 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 static String replace( 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 static String replace( 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/test/java/org/hibernate/test/util/StringHelperTest.java b/hibernate-core/src/test/java/org/hibernate/test/util/StringHelperTest.java index 299b60bc322c..545a63875388 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 @@ -100,4 +100,24 @@ public void testIsQuotedWithDialect() { Assert.assertFalse( StringHelper.isQuoted( "a", sqlServerDialect ) ); } + @Test + 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(); + } + } From 77462271156d8eeb595a92aaaebdf6b919f745cc Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Wed, 14 Aug 2019 11:04:47 +0100 Subject: [PATCH 16/99] HHH-13576 Invoking tracef() or debugf() w/o an array of parameters actually allocates an empty Object[] --- .../src/main/java/org/hibernate/internal/SessionImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 0589a070dcbd..324dcdee9163 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -1109,7 +1109,7 @@ public final Object internalLoad( 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(); } @@ -2512,7 +2512,7 @@ private Transaction getTransactionIfAccessible() { @Override public void beforeTransactionCompletion() { - log.tracef( "SessionImpl#beforeTransactionCompletion()" ); + log.trace( "SessionImpl#beforeTransactionCompletion()" ); flushBeforeTransactionCompletion(); actionQueue.beforeTransactionCompletion(); try { From e110ab00300107e46af52b98eb1874681d833e0c Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Wed, 14 Aug 2019 11:07:46 +0100 Subject: [PATCH 17/99] HHH-13576 A couple more Logger mistakes in SessionImpl --- .../org/hibernate/internal/SessionImpl.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) 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 324dcdee9163..f7b17e2bcb22 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -958,12 +958,14 @@ public void removeOrphanBeforeUpdates(String entityName, Object 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) { @@ -2526,7 +2528,9 @@ public void beforeTransactionCompletion() { @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 ) { From 6b489474f58033fe9b52e1c5c866b421c8c4f3e2 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Wed, 14 Aug 2019 11:31:32 +0100 Subject: [PATCH 18/99] HHH-13576 Similar debugf and tracef issues found via grepping --- .../boot/spi/XmlMappingBinderAccess.java | 4 ++-- .../main/java/org/hibernate/cfg/Settings.java | 2 +- .../internal/JtaPlatformInitiator.java | 4 ++-- .../internal/DefaultLoadEventListener.java | 2 +- .../EntityCopyObserverFactoryInitiator.java | 8 ++++---- .../org/hibernate/internal/IteratorImpl.java | 4 ++-- .../internal/SessionFactoryRegistry.java | 2 +- .../antlr/OrderByFragmentTranslator.java | 2 +- .../HibernatePersistenceProviderAdaptor.java | 18 ++++++++++-------- 9 files changed, 24 insertions(+), 22 deletions(-) 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 f2d7faf89ef4..8102865f2acd 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 Binding bind(InputStreamAccess xmlInputStreamAccess) { 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/cfg/Settings.java b/hibernate-core/src/main/java/org/hibernate/cfg/Settings.java index 046fa59c4658..c2d590d98489 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 Settings(SessionFactoryOptions sessionFactoryOptions, String defaultCatal 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/engine/transaction/jta/platform/internal/JtaPlatformInitiator.java b/hibernate-core/src/main/java/org/hibernate/engine/transaction/jta/platform/internal/JtaPlatformInitiator.java index c98d5a7fb76e..ee9b808779a6 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 JtaPlatform initiateService(Map configurationValues, ServiceRegistryImple JtaPlatform platform = registry.getService( StrategySelector.class ).resolveStrategy( JtaPlatform.class, setting ); if ( platform == null ) { - LOG.debugf( "No JtaPlatform was specified, checking resolver" ); + LOG.debug( "No JtaPlatform was specified, checking resolver" ); platform = registry.getService( JtaPlatformResolver.class ).resolveJtaPlatform( configurationValues, registry ); } if ( platform == null ) { - LOG.debugf( "No JtaPlatform was specified, checking resolver" ); + LOG.debug( "No JtaPlatform was specified, checking resolver" ); platform = getFallbackProvider( configurationValues, registry ); } 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 f40ca718d3e5..432b8650db92 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 @@ private Object proxyOrLoad( // 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/EntityCopyObserverFactoryInitiator.java b/hibernate-core/src/main/java/org/hibernate/event/internal/EntityCopyObserverFactoryInitiator.java index eb3156da3e51..942f2c9f36dd 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 @@ else if ( value.equals( EntityCopyAllowedLoggedObserver.SHORT_NAME ) || value.eq //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/internal/IteratorImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/IteratorImpl.java index e6db72e7ba15..5e62e4a30c08 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 Object next() throws HibernateException { 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 Object next() throws HibernateException { } 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/SessionFactoryRegistry.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryRegistry.java index e3d836ddd74f..ec94c1ceda42 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 SessionFactory getSessionFactory(String uuid) { 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/sql/ordering/antlr/OrderByFragmentTranslator.java b/hibernate-core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragmentTranslator.java index 69ae897682e4..ac8a029415bf 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragmentTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ordering/antlr/OrderByFragmentTranslator.java @@ -37,7 +37,7 @@ public class OrderByFragmentTranslator { * @return The translation. */ public static OrderByTranslation translate(TranslationContext context, String fragment) { - LOG.tracef( "Beginning parsing of order-by fragment : " + fragment ); + LOG.tracef( "Beginning parsing of order-by fragment : ", fragment ); GeneratedOrderByLexer lexer = new GeneratedOrderByLexer( new StringReader( fragment ) ); 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 10d0e5a43877..d8a330d830dd 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 void addProviderDependencies(PersistenceUnitMetadata pu) { 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 void addProviderDependencies(PersistenceUnitMetadata pu) { 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() + ); + } } } From cfd7db36c60b49ea773b3a1a34f062b91c0881c8 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Wed, 14 Aug 2019 09:29:20 +0100 Subject: [PATCH 19/99] HHH-13574 SybaseASE does not support PARTITION BY --- .../main/java/org/hibernate/dialect/SybaseASE15Dialect.java | 5 +++++ 1 file changed, 5 insertions(+) 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 4d75e1bdc3af..0027263e19e3 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 @@ protected SqlTypeDescriptor getSqlTypeDescriptorOverride(int sqlCode) { public boolean supportsLockTimeouts() { return false; } + + @Override + public boolean supportsPartitionBy() { + return false; + } } From 210aff098c2b1d1c59e7780829d9c19bfff08e46 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Wed, 14 Aug 2019 11:55:11 +0100 Subject: [PATCH 20/99] HHH-13577 LockTest.testContendedPessimisticLock and StatementIsClosedAfterALockExceptionTest.testStatementIsClosed tests fail on Sybase HHH-13577 : Re-enable LockTest for SybaseASE15Dialect --- .../java/org/hibernate/jpa/test/lock/LockTest.java | 1 - .../testing/transaction/TransactionUtil.java | 7 +++++++ .../org/hibernate/testing/util/ExceptionUtil.java | 12 +++++++----- 3 files changed, 14 insertions(+), 6 deletions(-) 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 dc6e41c28277..09c149d46cec 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 void testLockOptimisticForceIncrementDifferentEm() throws Exception { @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-testing/src/main/java/org/hibernate/testing/transaction/TransactionUtil.java b/hibernate-testing/src/main/java/org/hibernate/testing/transaction/TransactionUtil.java index 95e2ce9e5b2e..67de5eb55c3c 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.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 @@ else if( Dialect.getDialect() instanceof AbstractHANADialect ) { 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 cb244aecea68..c2ef8bef7930 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 static boolean isSqlLockTimeout(Exception e) { 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; } From 7c57047f4a039221bad4dbaddae0dee03c8848be Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Thu, 15 Aug 2019 01:28:23 -0700 Subject: [PATCH 21/99] HHH-13569 : Shorten table name that's too long for Oracle --- .../org/hibernate/test/annotations/embedded/EmbeddedTest.java | 2 +- .../org/hibernate/test/annotations/embedded/WealthyPerson.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) 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 7b4427266106..526a7e062d81 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 @@ -615,7 +615,7 @@ public void testEmbeddedAndOneToManyHql() throws Exception { 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 3b24b37908a7..05df8f1df60b 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 From 927f4c2ffc4ae5c45d2ede9d8302d52e7014085e Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Fri, 16 Aug 2019 11:57:37 +0100 Subject: [PATCH 22/99] HHH-13584 Reduce ServiceRegistry lookups in LocalConnectionAccess in SessionFactory --- .../internal/SessionFactoryImpl.java | 32 ++++++------------- 1 file changed, 9 insertions(+), 23 deletions(-) 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 1f380c8cd482..d311b4d18ad7 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.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; @@ -458,29 +459,14 @@ private void applyCfgXmlValues(LoadedConfig aggregatedConfig, SessionFactoryServ } 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 { From 0b64cef2b3d047fba460f984514446cc46a29db9 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Wed, 14 Aug 2019 13:36:37 +0100 Subject: [PATCH 23/99] HHH-13579 Optimise ResourceRegistryStandardImpl to avoid heavy allocation of iterators --- .../ResourceRegistryStandardImpl.java | 103 ++++++++++-------- 1 file changed, 58 insertions(+), 45 deletions(-) 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 e78f1590e30c..a40d738edaaa 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.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.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 boolean hasRegisteredResources() { 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 void register(Statement statement, boolean cancelable) { 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 void release(ResultSet resultSet, Statement statement) { } } if ( statement != null ) { - final Set resultSets = xref.get( statement ); + final HashMap resultSets = xref.get( statement ); if ( resultSets == null ) { log.unregisteredStatement(); } @@ -123,19 +138,16 @@ public void release(ResultSet resultSet, Statement statement) { } } 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 ); - } + protected void closeAll(final HashMap resultSets) { + resultSets.forEach( (resultSet, o) -> close( resultSet ) ); resultSets.clear(); } @@ -202,7 +214,7 @@ public void register(ResultSet resultSet, Statement statement) { } } 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 +222,14 @@ public void register(ResultSet resultSet, Statement statement) { 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 +315,63 @@ public void releaseResources() { jdbcObserver.jdbcReleaseRegistryResourcesStart(); } - for ( Map.Entry> entry : xref.entrySet() ) { - if ( entry.getValue() != null ) { - closeAll( entry.getValue() ); - } - close( entry.getKey() ); - } + xref.forEach( + (Statement s, HashMap r) -> { + closeAll( r ); + close( s ); + } + ); 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(); + } ); + nclobs = null; } - if ( jdbcObserver != null ) { + if ( jdbcObserver != null ) { jdbcObserver.jdbcReleaseRegistryResourcesEnd(); } } - 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(); } } From 0a1213be1aa2b8bda4cdc3219dab573cd5b9adad Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Fri, 16 Aug 2019 14:59:37 +0100 Subject: [PATCH 24/99] HHH-13585 Duplicate resource release in PessimisticReadSelectLockingStrategy --- .../PessimisticReadSelectLockingStrategy.java | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) 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 0b1b6d3d2fdc..319967ba8276 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 void lock(Serializable id, Object version, Object object, int timeout, Sh } 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 { From 0a3f62abbffacb0fcd8d611942fce46f5dea5761 Mon Sep 17 00:00:00 2001 From: Sergey Ponomarev Date: Sat, 17 Aug 2019 19:38:30 +0300 Subject: [PATCH 25/99] HHH-13588 Add missed functions to MySQL Dialect: weight_string, to_base64, from_base64, regexp_replace, regexp_instr, regexp_substr --- .../main/java/org/hibernate/dialect/MySQL57Dialect.java | 7 +++++++ .../src/main/java/org/hibernate/dialect/MySQL8Dialect.java | 6 ++++++ 2 files changed, 13 insertions(+) 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 2adf0514bc78..8f9ac9742a9c 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 @@ 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 MySQL57Dialect() { // 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 4d145c11fc9f..dcda2359b6f6 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 MySQL8Dialect() { 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 From f49c97c0aa6777677dc5adc1eb57586acff06f80 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Sun, 18 Aug 2019 14:46:37 +0100 Subject: [PATCH 26/99] HHH-13589 ActionQueue review: code formatting --- .../org/hibernate/engine/spi/ActionQueue.java | 90 +++++++++---------- 1 file changed, 45 insertions(+), 45 deletions(-) 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 909a7efc8eb3..1e7ed134c50e 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 @@ -231,11 +231,11 @@ public ActionQueue(SessionImplementor session) { public void clear() { for ( ListProvider listProvider : EXECUTABLE_LISTS_MAP.values() ) { 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 @@ private void addInsertAction(AbstractEntityInsertAction insert) { 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 @@ private void addResolvedEntityInsertAction(AbstractEntityInsertAction insert) { 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 void addAction(BulkOperationCleanupAction action) { } 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 @@ private void registerCleanupActions(Executable executable) { 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 boolean hasUnresolvedEntityInsertActions() { * 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 ); @@ -493,7 +493,7 @@ public void prepareActions() throws HibernateException { } private void prepareActions(ExecutableList queue) throws HibernateException { - if( queue == null ) { + if ( queue == null ) { return; } for ( Executable executable : queue ) { @@ -509,7 +509,7 @@ private void prepareActions(ExecutableList queue) throws HibernateException { 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 void afterTransactionCompletion(boolean success) { 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 boolean areTablesToBeUpdated(@SuppressWarnings("rawtypes") Set tables) { return true; } } - if(unresolvedInsertions == null) { + if ( unresolvedInsertions == null ) { return false; } return areTablesToBeUpdated( unresolvedInsertions, tables ); @@ -604,14 +604,14 @@ private & Serializable> void executeAction 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 > void execute(E executable) { 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 @@ private static String toString(ExecutableList q) { } 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 int numberOfDeletions() { } 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 void setTransactionCompletionProcesses(TransactionCompletionProcesses pro 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 @@ private boolean isOrderInsertsEnabled() { } 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 void unScheduleDeletion(EntityEntry entry, Object rescuedEntity) { 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 void unScheduleDeletion(EntityEntry entry, Object rescuedEntity) { */ 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 static ActionQueue deserialize(ObjectInputStream ois, SessionImplementor 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 void sort(List insertions) { 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 void sort(List insertions) { } 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. " + From d06588814029175c95dd901c93fa66c573925833 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Sun, 18 Aug 2019 15:00:09 +0100 Subject: [PATCH 27/99] HHH-13589 Avoid HashMap.values() when forEach is an easy replacement --- .../main/java/org/hibernate/engine/spi/ActionQueue.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 1e7ed134c50e..8b7c3698f489 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,12 +229,12 @@ public ActionQueue(SessionImplementor session) { } public void clear() { - for ( ListProvider listProvider : EXECUTABLE_LISTS_MAP.values() ) { + EXECUTABLE_LISTS_MAP.forEach( (k,listProvider) -> { ExecutableList l = listProvider.get( this ); if ( l != null ) { l.clear(); } - } + } ); if ( unresolvedInsertions != null ) { unresolvedInsertions.clear(); } @@ -472,12 +472,12 @@ public void executeActions() throws HibernateException { 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 ); } - } + } ); } /** From eb675e1c7f27f9a086af5ed935d0d41d2afb817a Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Sat, 17 Aug 2019 13:55:29 +0100 Subject: [PATCH 28/99] HHH-13587 Review formatting and logging style of StatefulPersistenceContext --- .../internal/StatefulPersistenceContext.java | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) 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 b071c9a9f7f6..78c001a81214 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 @@ -58,6 +58,7 @@ 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 +79,7 @@ * their processing. * * @author Steve Ebersole + * @author Sanne Grinovero */ public class StatefulPersistenceContext implements PersistenceContext { private static final CoreMessageLogger LOG = Logger.getMessageLogger( @@ -199,7 +201,7 @@ public LoadContexts getLoadContexts() { @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,8 +214,8 @@ public PersistentCollection useUnownedCollection(CollectionKey key) { @Override public BatchFetchQueue getBatchFetchQueue() { - if (batchFetchQueue==null) { - batchFetchQueue = new BatchFetchQueue(this); + if ( batchFetchQueue == null ) { + batchFetchQueue = new BatchFetchQueue( this ); } return batchFetchQueue; } @@ -417,8 +419,8 @@ public Object removeEntity(EntityKey key) { entitySnapshotsByKey.remove( key ); nullifiableEntityKeys.remove( key ); if( batchFetchQueue != null ) { - getBatchFetchQueue().removeBatchLoadableEntityKey(key); - getBatchFetchQueue().removeSubselect(key); + getBatchFetchQueue().removeBatchLoadableEntityKey( key ); + getBatchFetchQueue().removeSubselect( key ); } return entity; } @@ -938,9 +940,7 @@ public void addNonLazyCollection(PersistentCollection 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 @@ -1278,8 +1278,9 @@ private boolean isFoundInParent( @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 ); @@ -1289,7 +1290,7 @@ public Object getIndexInOwner(String entity, String property, Object childEntity 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 ) { @@ -1378,7 +1379,7 @@ public boolean isReadOnly(Object entityOrProxy) { 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 +1416,12 @@ public void setReadOnly(Object object, boolean readOnly) { } 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) { @@ -1461,9 +1463,7 @@ public void replaceDelayedEntityIdentityInsertKeys(EntityKey oldKey, Serializabl * @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 ); @@ -1556,9 +1556,7 @@ public void serialize(ObjectOutputStream oos) throws IOException { 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(); @@ -1848,7 +1846,7 @@ private void managedSharedCacheEntries( 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 +1912,9 @@ public void doAfterTransactionCompletion(boolean success, SharedSessionContractI break; } default: { - LOG.debug( "Unexpected CachedNaturalIdValueSource [" + source + "]" ); + if ( LOG.isDebugEnabled() ) { + LOG.debug( "Unexpected CachedNaturalIdValueSource [" + source + "]" ); + } } } } From 5bbf417c523c938899a66ecf7b148b41fa880bb7 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Sat, 17 Aug 2019 12:03:25 +0100 Subject: [PATCH 29/99] HHH-13587 Allocate StatefulPersistenceContext#nullifiableEntityKeys lazily --- .../engine/internal/AbstractEntityEntry.java | 3 +- .../engine/internal/ForeignKeys.java | 2 +- .../internal/StatefulPersistenceContext.java | 52 +++++++++++++++---- .../engine/spi/PersistenceContext.java | 25 +++++++++ .../internal/DefaultDeleteEventListener.java | 2 +- 5 files changed, 72 insertions(+), 12 deletions(-) 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 c5045b4f4635..f3c8e4f240ee 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 @@ 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 @@ else if ( earlyInsert ) { 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 860350e0380d..814cccf23831 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 @@ private Object initializeIfNecessary( 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/StatefulPersistenceContext.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java index 78c001a81214..969c24678484 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 @@ -21,6 +21,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentMap; +import java.util.function.Supplier; import org.hibernate.AssertionFailure; import org.hibernate.Hibernate; @@ -175,8 +176,6 @@ public StatefulPersistenceContext(SharedSessionContractImplementor session) { 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 ); } @@ -255,7 +254,7 @@ public void clear() { unownedCollections.clear(); } proxiesByKey.clear(); - nullifiableEntityKeys.clear(); + nullifiableEntityKeys = null; if ( batchFetchQueue != null ) { batchFetchQueue.clear(); } @@ -417,7 +416,9 @@ public Object removeEntity(EntityKey key) { // Clear all parent cache parentsByChild.clear(); entitySnapshotsByKey.remove( key ); - nullifiableEntityKeys.remove( key ); + if ( nullifiableEntityKeys != null ) { + nullifiableEntityKeys.remove( key ); + } if( batchFetchQueue != null ) { getBatchFetchQueue().removeBatchLoadableEntityKey( key ); getBatchFetchQueue().removeSubselect( key ); @@ -1027,6 +1028,9 @@ public Object removeProxy(EntityKey key) { @Override public HashSet getNullifiableEntityKeys() { + if ( nullifiableEntityKeys == null ) { + nullifiableEntityKeys = new HashSet<>(); + } return nullifiableEntityKeys; } @@ -1533,12 +1537,18 @@ public void serialize(ObjectOutputStream oos) throws IOException { 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 ); + } } } @@ -1720,6 +1730,30 @@ public boolean wasInsertedDuringTransaction(EntityPersister persister, Serializa 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 ); + } + private void cleanUpInsertedKeysAfterTransaction() { if ( insertedKeysMap != null ) { insertedKeysMap.clear(); 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 b3a4589aa179..52c25a4114c9 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,7 @@ import java.util.Collection; import java.util.HashSet; import java.util.Map; +import java.util.function.Supplier; import org.hibernate.HibernateException; import org.hibernate.LockMode; @@ -477,7 +478,9 @@ CollectionEntry addInitializedCollection(CollectionPersister persister, /** * Retrieve the set of EntityKeys representing nullifiable references + * @deprecated Use {@link #containsNullifiableEntityKey(Supplier)} or {@link #registerNullifiableEntityKey(EntityKey)} or {@link #isNullifiableEntityKeysEmpty()} */ + @Deprecated HashSet getNullifiableEntityKeys(); /** @@ -720,6 +723,28 @@ CollectionEntry addInitializedCollection(CollectionPersister persister, */ 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(); + /** * Provides centralized access to natural-id-related functionality. */ 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 c1616245bc44..eb09b3bf02dc 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 @@ protected final void deleteEntity( 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 From aae670b9b33dca97ac9624eac3e62ca23532663c Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Sat, 17 Aug 2019 15:53:07 +0100 Subject: [PATCH 30/99] HHH-13587 Allocate StatefulPersistenceContext#nullAssociations lazily --- .../engine/internal/StatefulPersistenceContext.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) 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 969c24678484..b8cde15c70de 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 @@ -176,7 +176,6 @@ public StatefulPersistenceContext(SharedSessionContractImplementor session) { collectionsByKey = new HashMap<>( INIT_COLL_SIZE ); arrayHolders = new IdentityHashMap<>( INIT_COLL_SIZE ); - nullAssociations = new HashSet<>( INIT_COLL_SIZE ); nonlazyCollections = new ArrayList<>( INIT_COLL_SIZE ); } @@ -1359,16 +1358,19 @@ private Object getIndexInParent( @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 From 0538b97d6a087f77e2ca51248ef3317c6f0973a8 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Sat, 17 Aug 2019 15:58:36 +0100 Subject: [PATCH 31/99] HHH-13587 Allocate StatefulPersistenceContext#naturalIdXrefDelegate lazily --- .../internal/StatefulPersistenceContext.java | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) 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 b8cde15c70de..0a681cf470b8 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 @@ -262,7 +262,7 @@ public void clear() { if ( loadContexts != null ) { loadContexts.cleanup(); } - naturalIdXrefDelegate.clear(); + naturalIdXrefDelegate = null; } @Override @@ -1762,11 +1762,16 @@ private void cleanUpInsertedKeysAfterTransaction() { } } - - // 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 @@ -1785,7 +1790,7 @@ public void cacheNaturalIdCrossReferenceFromLoad( // 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 ); @@ -1808,7 +1813,7 @@ public void manageLocalNaturalIdCrossReference( final Object[] naturalIdValues = extractNaturalIdValues( state, persister ); // cache - naturalIdXrefDelegate.cacheNaturalIdCrossReference( persister, id, naturalIdValues ); + getNaturalIdXrefDelegate().cacheNaturalIdCrossReference( persister, id, naturalIdValues ); } @Override @@ -1965,7 +1970,7 @@ public Object[] removeLocalNaturalIdCrossReference(EntityPersister persister, Se persister = locateProperPersister( persister ); final Object[] naturalIdValues = getNaturalIdValues( state, persister ); - final Object[] localNaturalIdValues = naturalIdXrefDelegate.removeNaturalIdCrossReference( + final Object[] localNaturalIdValues = getNaturalIdXrefDelegate().removeNaturalIdCrossReference( persister, id, naturalIdValues @@ -2004,12 +2009,12 @@ public void removeSharedNaturalIdCrossReference(EntityPersister persister, Seria @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 @@ -2047,7 +2052,7 @@ public Object[] extractNaturalIdValues(Object entity, EntityPersister persister) @Override public Collection getCachedPkResolutions(EntityPersister entityPersister) { - return naturalIdXrefDelegate.getCachedPkResolutions( entityPersister ); + return getNaturalIdXrefDelegate().getCachedPkResolutions( entityPersister ); } @Override @@ -2060,6 +2065,7 @@ public void handleSynchronization(EntityPersister persister, Serializable pk, Ob persister = locateProperPersister( persister ); final Object[] naturalIdValuesFromCurrentObjectState = extractNaturalIdValues( entity, persister ); + final NaturalIdXrefDelegate naturalIdXrefDelegate = getNaturalIdXrefDelegate(); final boolean changed = ! naturalIdXrefDelegate.sameAsCached( persister, pk, @@ -2081,12 +2087,12 @@ public void handleSynchronization(EntityPersister persister, Serializable pk, Ob @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 ) From a11359524b5325834c4c2ac673f15a7a7004d9a0 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Sat, 17 Aug 2019 16:17:17 +0100 Subject: [PATCH 32/99] HHH-13587 Lazily initialize ConcurrentReferenceHashMap proxiesByKey in StatefulPersistenceContext --- .../internal/StatefulPersistenceContext.java | 93 ++++++++++--------- 1 file changed, 49 insertions(+), 44 deletions(-) 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 0a681cf470b8..2aed5a267342 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 @@ -157,15 +157,6 @@ public StatefulPersistenceContext(SharedSessionContractImplementor 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 ); @@ -179,6 +170,21 @@ public StatefulPersistenceContext(SharedSessionContractImplementor session) { 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 public boolean isStateless() { return false; @@ -220,12 +226,12 @@ public BatchFetchQueue getBatchFetchQueue() { @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() ) { @@ -252,7 +258,7 @@ public void clear() { if ( unownedCollections != null ) { unownedCollections.clear(); } - proxiesByKey.clear(); + proxiesByKey = null; nullifiableEntityKeys = null; if ( batchFetchQueue != null ) { batchFetchQueue.clear(); @@ -566,7 +572,7 @@ public boolean containsCollection(PersistentCollection collection) { @Override public boolean containsProxy(Object entity) { - return proxiesByKey.containsValue( entity ); + return proxiesByKey != null && proxiesByKey.containsValue( entity ); } @Override @@ -618,7 +624,7 @@ private void reassociateProxy(LazyInitializer li, HibernateProxy proxy) { 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 ); } } @@ -688,7 +694,7 @@ public Object narrowProxy(Object proxy, EntityPersister persister, EntityKey key // 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; } @@ -699,7 +705,7 @@ public Object narrowProxy(Object proxy, EntityPersister persister, EntityKey key final Object impl = originalHibernateProxy.getHibernateLazyInitializer().getImplementation(); // can we return it? if ( concreteProxyClass.isInstance( impl ) ) { - proxiesByKey.remove( key ); + removeProxyByKey( key ); return impl; } } @@ -724,12 +730,19 @@ public Object narrowProxy(Object proxy, EntityPersister persister, EntityKey key } } + 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; } @@ -1008,12 +1021,12 @@ public CollectionEntry getCollectionEntryOrNull(Object collection) { @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,7 +1035,7 @@ public Object removeProxy(EntityKey key) { batchFetchQueue.removeBatchLoadableEntityKey( key ); batchFetchQueue.removeSubselect( key ); } - return proxiesByKey.remove( key ); + return removeProxyByKey( key ); } @Override @@ -1038,10 +1051,6 @@ public Map getEntitiesByKey() { return entitiesByKey; } - public Map getProxiesByKey() { - return proxiesByKey; - } - @Override public int getNumberOfManagedEntities() { return entityEntryContext.getNumberOfManagedEntities(); @@ -1492,13 +1501,18 @@ public void serialize(ObjectOutputStream oos) throws IOException { 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() ); @@ -1604,21 +1618,12 @@ public static StatefulPersistenceContext deserialize( 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 From 1101727a4f7584944500d35f7105393c24f7c23d Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Sat, 17 Aug 2019 16:43:15 +0100 Subject: [PATCH 33/99] HHH-13587 StatefulPersistenceContext#unownedCollections was almost fully lazy already --- .../hibernate/engine/internal/StatefulPersistenceContext.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 2aed5a267342..49c32a27bd7d 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 @@ -255,9 +255,7 @@ public void clear() { entitySnapshotsByKey.clear(); collectionsByKey.clear(); collectionEntries.clear(); - if ( unownedCollections != null ) { - unownedCollections.clear(); - } + unownedCollections = null; proxiesByKey = null; nullifiableEntityKeys = null; if ( batchFetchQueue != null ) { From 0d10174c236d4d73d7f64893819687a663ce1be7 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Sat, 17 Aug 2019 16:53:01 +0100 Subject: [PATCH 34/99] HHH-13587 Make StatefulPersistenceContext#parentsByChild a lazily initialized IdentityHashMap --- .../internal/StatefulPersistenceContext.java | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) 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 49c32a27bd7d..c9df3b968a3c 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 @@ -162,7 +162,6 @@ public StatefulPersistenceContext(SharedSessionContractImplementor session) { 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 ); @@ -250,8 +249,7 @@ public void clear() { entitiesByKey.clear(); entitiesByUniqueKey.clear(); entityEntryContext.clear(); -// entityEntries.clear(); - parentsByChild.clear(); + parentsByChild = null; entitySnapshotsByKey.clear(); collectionsByKey.clear(); collectionEntries.clear(); @@ -417,7 +415,7 @@ public Object removeEntity(EntityKey key) { } } // Clear all parent cache - parentsByChild.clear(); + parentsByChild = null; entitySnapshotsByKey.remove( key ); if ( nullifiableEntityKeys != null ) { nullifiableEntityKeys.remove( key ); @@ -1173,7 +1171,7 @@ public Serializable getOwnerId(String entityName, String propertyName, Object ch 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 @@ -1183,7 +1181,7 @@ && isFoundInParent( propertyName, childEntity, persister, collectionPersister, p } else { // remove wrong entry - parentsByChild.remove( childEntity ); + removeChildParent( childEntity ); } } @@ -1274,6 +1272,13 @@ && isFoundInParent( propertyName, childEntity, persister, collectionPersister, p return null; } + private Object getParentsByChild(Object childEntity) { + if ( parentsByChild != null ) { + parentsByChild.get( childEntity ); + } + return null; + } + private boolean isFoundInParent( String property, Object childEntity, @@ -1293,7 +1298,7 @@ public Object getIndexInOwner(String entity, String property, Object childEntity 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 @@ -1317,7 +1322,7 @@ public Object getIndexInOwner(String entity, String property, Object childEntity } else { // remove wrong entry - parentsByChild.remove( childEntity ); + removeChildParent( childEntity ); } } @@ -1450,7 +1455,7 @@ private void setEntityReadOnly(Object entity, boolean readOnly) { 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 ); @@ -1691,15 +1696,19 @@ public static StatefulPersistenceContext deserialize( @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; From 3b30c60ce3e81b23c3eea21018d5831b8bb4c5ad Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Sat, 17 Aug 2019 17:00:13 +0100 Subject: [PATCH 35/99] HHH-13587 Introduce SPI method PersistenceContext#getCollectionEntriesSize() --- .../internal/StatefulPersistenceContext.java | 5 +++++ .../engine/spi/PersistenceContext.java | 7 +++++++ .../AbstractFlushingEventListener.java | 2 +- .../DefaultAutoFlushEventListener.java | 2 +- .../internal/DefaultFlushEventListener.java | 2 +- .../stat/internal/SessionStatisticsImpl.java | 2 +- .../TestAutoFlushBeforeQueryExecution.java | 18 +++++++++--------- 7 files changed, 25 insertions(+), 13 deletions(-) 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 c9df3b968a3c..9ea1d7d22ac1 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 @@ -1768,6 +1768,11 @@ public boolean isNullifiableEntityKeysEmpty() { return ( nullifiableEntityKeys == null || nullifiableEntityKeys.size() == 0 ); } + @Override + public int getCollectionEntriesSize() { + return collectionEntries == null ? 0 : collectionEntries.size(); + } + private void cleanUpInsertedKeysAfterTransaction() { if ( insertedKeysMap != null ) { insertedKeysMap.clear(); 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 52c25a4114c9..7c687a771a03 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 @@ -745,6 +745,13 @@ CollectionEntry addInitializedCollection(CollectionPersister persister, */ 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(); + /** * Provides centralized access to natural-id-related functionality. */ diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractFlushingEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractFlushingEventListener.java index fcbc02712eb1..701688a6a179 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractFlushingEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractFlushingEventListener.java @@ -125,7 +125,7 @@ private void logFlushResults(FlushEvent event) { session.getActionQueue().numberOfCollectionCreations(), session.getActionQueue().numberOfCollectionUpdates(), session.getActionQueue().numberOfCollectionRemovals(), - persistenceContext.getCollectionEntries().size() + persistenceContext.getCollectionEntriesSize() ); new EntityPrinter( session.getFactory() ).toString( persistenceContext.getEntitiesByKey().entrySet() 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 365a010bee32..3683bbfd9f8b 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 @@ -89,6 +89,6 @@ private boolean flushMightBeNeeded(final EventSource source) { 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/DefaultFlushEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEventListener.java index fc8889d5d27b..4d32fa7bfc0e 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 void onFlush(FlushEvent event) throws HibernateException { 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/stat/internal/SessionStatisticsImpl.java b/hibernate-core/src/main/java/org/hibernate/stat/internal/SessionStatisticsImpl.java index 3eb1055513e5..fd6da5cca8fe 100755 --- a/hibernate-core/src/main/java/org/hibernate/stat/internal/SessionStatisticsImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/stat/internal/SessionStatisticsImpl.java @@ -28,7 +28,7 @@ public int getEntityCount() { } public int getCollectionCount() { - return session.getPersistenceContextInternal().getCollectionEntries().size(); + return session.getPersistenceContextInternal().getCollectionEntriesSize(); } public Set getEntityKeys() { 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 7d2ec3cd30e9..1a0631e80f3e 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 void testAutoflushIsRequired() { 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 void testAutoflushIsRequired() { "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 void testAutoflushIsRequired() { 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 void testAutoflushIsRequired() { 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 void testAutoflushIsNotRequiredWithUnrelatedCollectionChange() { 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 void testAutoflushIsNotRequiredWithUnrelatedCollectionChange() { 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 void testAutoflushIsNotRequiredWithUnrelatedCollectionChange() { 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 void testAutoflushIsNotRequiredWithUnrelatedCollectionChange() { 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 void testAutoflushIsNotRequiredWithUnrelatedCollectionChange() { 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() ) ); From 690a8d55207c2ca5a195508daaeac675ef93c54e Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Sat, 17 Aug 2019 17:07:20 +0100 Subject: [PATCH 36/99] HHH-13587 Make StatefulPersistenceContext#collectionEntries also lazily initialized --- .../internal/StatefulPersistenceContext.java | 83 +++++++-- .../engine/spi/PersistenceContext.java | 17 ++ .../AbstractFlushingEventListener.java | 176 ++++++++---------- .../event/internal/EvictVisitor.java | 2 +- .../util/collections/IdentityMap.java | 10 + 5 files changed, 172 insertions(+), 116 deletions(-) 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 9ea1d7d22ac1..b072382e1162 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 @@ -21,6 +21,7 @@ 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; @@ -160,9 +161,6 @@ public StatefulPersistenceContext(SharedSessionContractImplementor session) { entitySnapshotsByKey = new HashMap<>( INIT_COLL_SIZE ); entityEntryContext = new EntityEntryContext( this ); -// entityEntries = IdentityMap.instantiateSequenced( INIT_COLL_SIZE ); - collectionEntries = IdentityMap.instantiateSequenced( INIT_COLL_SIZE ); - collectionsByKey = new HashMap<>( INIT_COLL_SIZE ); arrayHolders = new IdentityHashMap<>( INIT_COLL_SIZE ); @@ -243,7 +241,9 @@ public void clear() { } final SharedSessionContractImplementor session = getSession(); - IdentityMap.onEachKey( collectionEntries, k -> k.unsetSession( session ) ); + if ( collectionEntries != null ) { + IdentityMap.onEachKey( collectionEntries, k -> k.unsetSession( session ) ); + } arrayHolders.clear(); entitiesByKey.clear(); @@ -252,7 +252,7 @@ public void clear() { parentsByChild = null; entitySnapshotsByKey.clear(); collectionsByKey.clear(); - collectionEntries.clear(); + collectionEntries = null; unownedCollections = null; proxiesByKey = null; nullifiableEntityKeys = null; @@ -454,7 +454,7 @@ public boolean isEntryFor(Object entity) { @Override public CollectionEntry getCollectionEntry(PersistentCollection coll) { - return collectionEntries.get( coll ); + return collectionEntries == null ? null : collectionEntries.get( coll ); } @Override @@ -563,7 +563,7 @@ public EntityEntry addReferenceEntry( @Override public boolean containsCollection(PersistentCollection collection) { - return collectionEntries.containsKey( collection ); + return collectionEntries != null && collectionEntries.containsKey( collection ); } @Override @@ -888,7 +888,7 @@ public void addNewCollection(CollectionPersister persister, PersistentCollection * @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 ) { @@ -897,12 +897,21 @@ private void addCollection(PersistentCollection coll, CollectionEntry entry, Ser } // 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 * @@ -911,7 +920,7 @@ private void addCollection(PersistentCollection coll, CollectionEntry entry, Ser */ private void addCollection(PersistentCollection collection, CollectionPersister persister) { final CollectionEntry ce = new CollectionEntry( persister, collection ); - collectionEntries.put( collection, ce ); + getOrInitializeCollectionEntries().put( collection, ce ); } @Override @@ -998,7 +1007,7 @@ public CollectionEntry getCollectionEntryOrNull(Object collection) { } 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(); @@ -1057,9 +1066,29 @@ public Map getEntityEntries() { 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 @@ -1538,13 +1567,18 @@ public void serialize(ObjectOutputStream oos) throws IOException { 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() ); @@ -1660,12 +1694,11 @@ public static StatefulPersistenceContext deserialize( 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(); @@ -1773,6 +1806,16 @@ 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(); 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 7c687a771a03..44057ed85e86 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,7 @@ 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; @@ -510,9 +511,18 @@ CollectionEntry addInitializedCollection(CollectionPersister persister, /** * 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 */ @@ -752,6 +762,13 @@ CollectionEntry addInitializedCollection(CollectionPersister persister, */ 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/event/internal/AbstractFlushingEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractFlushingEventListener.java index 701688a6a179..b3120d7c5dde 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractFlushingEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/AbstractFlushingEventListener.java @@ -11,11 +11,11 @@ import java.util.Map; import org.hibernate.HibernateException; +import org.hibernate.Interceptor; import org.hibernate.action.internal.CollectionRecreateAction; import org.hibernate.action.internal.CollectionRemoveAction; import org.hibernate.action.internal.CollectionUpdateAction; import org.hibernate.action.internal.QueuedOperationCollectionAction; -import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.internal.Cascade; import org.hibernate.engine.internal.CascadePoint; import org.hibernate.engine.internal.Collections; @@ -23,7 +23,6 @@ import org.hibernate.engine.spi.ActionQueue; import org.hibernate.engine.spi.CascadingAction; import org.hibernate.engine.spi.CascadingActions; -import org.hibernate.engine.spi.CollectionEntry; import org.hibernate.engine.spi.CollectionKey; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.PersistenceContext; @@ -38,7 +37,6 @@ import org.hibernate.event.spi.FlushEvent; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.EntityPrinter; -import org.hibernate.internal.util.collections.IdentityMap; import org.hibernate.internal.util.collections.LazyIterator; import org.hibernate.persister.entity.EntityPersister; @@ -193,11 +191,9 @@ private void prepareCollectionFlushes(PersistenceContext persistenceContext) thr // and reset reached, doupdate, etc. LOG.debug( "Dirty checking collections" ); - - for ( Map.Entry entry : - IdentityMap.concurrentEntries( (Map) persistenceContext.getCollectionEntries() ) ) { - entry.getValue().preFlush( entry.getKey() ); - } + persistenceContext.forEachCollectionEntry( (pc,ce) -> { + ce.preFlush( pc ); + }, true ); } /** @@ -252,78 +248,70 @@ private int flushEntities(final FlushEvent event, final PersistenceContext persi 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 @@ protected void postFlush(SessionImplementor session) throws HibernateException { // 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/EvictVisitor.java b/hibernate-core/src/main/java/org/hibernate/event/internal/EvictVisitor.java index d47d5498e640..fa7793b1ebe1 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 @@ else if ( value == LazyPropertyInitializer.UNFETCHED_PROPERTY ) { 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/internal/util/collections/IdentityMap.java b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/IdentityMap.java index eeda7b1ab1e1..d64786bbaffe 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.LinkedHashMap; import java.util.Map; import java.util.Set; +import java.util.function.BiConsumer; import java.util.function.Consumer; /** @@ -62,6 +63,15 @@ public static void onEachKey(Map map, Consumer consumer) { 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() ); } From dbbc24c2e16bc99606aaf7e17fc3c1b8cabf31c5 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Sat, 17 Aug 2019 21:26:42 +0100 Subject: [PATCH 37/99] HHH-13587 Make StatefulPersistenceContext#nonlazyCollections a lazily initialized field --- .../engine/internal/StatefulPersistenceContext.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) 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 b072382e1162..f9663713cc2f 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 @@ -163,8 +163,6 @@ public StatefulPersistenceContext(SharedSessionContractImplementor session) { entityEntryContext = new EntityEntryContext( this ); collectionsByKey = new HashMap<>( INIT_COLL_SIZE ); arrayHolders = new IdentityHashMap<>( INIT_COLL_SIZE ); - - nonlazyCollections = new ArrayList<>( INIT_COLL_SIZE ); } private ConcurrentMap getOrInitializeProxiesByKey() { @@ -252,6 +250,7 @@ public void clear() { parentsByChild = null; entitySnapshotsByKey.clear(); collectionsByKey.clear(); + nonlazyCollections = null; collectionEntries = null; unownedCollections = null; proxiesByKey = null; @@ -952,6 +951,9 @@ public PersistentCollection getCollection(CollectionKey collectionKey) { @Override public void addNonLazyCollection(PersistentCollection collection) { + if ( nonlazyCollections == null ) { + nonlazyCollections = new ArrayList<>( INIT_COLL_SIZE ); + } nonlazyCollections.add( collection ); } @@ -965,7 +967,7 @@ public void initializeNonLazyCollections() throws HibernateException { 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(); } From 05b888e0c0a66522ea921a808a801a3f45172b5b Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Sat, 17 Aug 2019 21:59:07 +0100 Subject: [PATCH 38/99] HHH-13587 Make StatefulPersistenceContext#arrayHolders lazily initialized as well --- .../internal/StatefulPersistenceContext.java | 35 ++++++++++++------- 1 file changed, 22 insertions(+), 13 deletions(-) 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 f9663713cc2f..0509877999cd 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 @@ -162,7 +162,6 @@ public StatefulPersistenceContext(SharedSessionContractImplementor session) { entityEntryContext = new EntityEntryContext( this ); collectionsByKey = new HashMap<>( INIT_COLL_SIZE ); - arrayHolders = new IdentityHashMap<>( INIT_COLL_SIZE ); } private ConcurrentMap getOrInitializeProxiesByKey() { @@ -243,7 +242,7 @@ public void clear() { IdentityMap.onEachKey( collectionEntries, k -> k.unsetSession( session ) ); } - arrayHolders.clear(); + arrayHolders = null; entitiesByKey.clear(); entitiesByUniqueKey.clear(); entityEntryContext.clear(); @@ -981,18 +980,21 @@ public void initializeNonLazyCollections() throws HibernateException { @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 @@ -1583,13 +1585,18 @@ public void serialize(ObjectOutputStream oos) throws IOException { } } - 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() ); + } } if ( nullifiableEntityKeys == null ) { @@ -1707,9 +1714,11 @@ public static StatefulPersistenceContext deserialize( 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(); From 7531ed793a9ab08c18f10491af293274f018d691 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Sat, 17 Aug 2019 21:50:58 +0100 Subject: [PATCH 39/99] HHH-13587 Make StatefulPersistenceContext#entitiesByUniqueKey lazily initialized as well --- .../internal/StatefulPersistenceContext.java | 45 ++++++++++++------- 1 file changed, 29 insertions(+), 16 deletions(-) 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 0509877999cd..dabcc510c611 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.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; @@ -157,7 +158,6 @@ public StatefulPersistenceContext(SharedSessionContractImplementor session) { this.session = session; entitiesByKey = new HashMap<>( INIT_COLL_SIZE ); - entitiesByUniqueKey = new HashMap<>( INIT_COLL_SIZE ); entitySnapshotsByKey = new HashMap<>( INIT_COLL_SIZE ); entityEntryContext = new EntityEntryContext( this ); @@ -244,7 +244,7 @@ public void clear() { arrayHolders = null; entitiesByKey.clear(); - entitiesByUniqueKey.clear(); + entitiesByUniqueKey = null; entityEntryContext.clear(); parentsByChild = null; entitySnapshotsByKey.clear(); @@ -406,12 +406,15 @@ public boolean containsEntity(EntityKey key) { @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 = null; entitySnapshotsByKey.remove( key ); @@ -427,11 +430,14 @@ public Object removeEntity(EntityKey key) { @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 ); } @@ -1528,13 +1534,18 @@ public void serialize(ObjectOutputStream oos) throws IOException { 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() ); + } } if ( proxiesByKey == null ) { @@ -1655,9 +1666,11 @@ public static StatefulPersistenceContext deserialize( 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(); From da847f4b574c3b21eb8ddd8d12b77011e2f63a53 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Sat, 17 Aug 2019 22:43:04 +0100 Subject: [PATCH 40/99] HHH-13587 Avoid using deprecated PersistenceContext#getCollectionEntries in tests --- .../MultipleSessionCollectionWarningTest.java | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) 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 9cd6db69f88b..e7a0090c74f0 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.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 void testSetCurrentSessionOverwritesNonConnectedSesssion() { // 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 void testSetCurrentSessionOverwritesNonConnectedSesssionFlushed() { // 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 void testUnsetSessionCannotOverwriteNonConnectedSesssion() { // 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 From e0f40474296c44cc8ec5e36256d3e0ddf118c7b8 Mon Sep 17 00:00:00 2001 From: Ladislav Kulhanek Date: Thu, 11 Oct 2018 15:49:44 +0200 Subject: [PATCH 41/99] HHH-12993 Omit joining of superclass table when querying subclass only --- .../SessionFactoryOptionsBuilder.java | 9 + ...stractDelegatingSessionFactoryOptions.java | 5 + .../boot/spi/SessionFactoryOptions.java | 2 + .../org/hibernate/cfg/AvailableSettings.java | 15 + .../engine/internal/JoinSequence.java | 12 +- .../ast/tree/AbstractMapComponentNode.java | 17 ++ .../hql/internal/ast/tree/DotNode.java | 36 +++ .../hql/internal/ast/tree/FromElement.java | 4 + .../internal/ast/tree/FromElementType.java | 10 + .../internal/ast/tree/FromReferenceNode.java | 10 + .../hql/internal/ast/util/ASTPrinter.java | 44 ++- .../ast/util/ASTReferencedTablesPrinter.java | 72 +++++ .../hql/internal/ast/util/JoinProcessor.java | 87 ++++++ .../hql/internal/ast/util/TokenPrinters.java | 2 + .../entity/AbstractEntityPersister.java | 71 ++++- .../hibernate/persister/entity/Joinable.java | 13 + .../entity/JoinedSubclassEntityPersister.java | 6 + .../entity/SingleTableEntityPersister.java | 1 + .../entity/UnionSubclassEntityPersister.java | 2 + .../java/org/hibernate/test/hql/HQLTest.java | 143 +++++++--- .../OmitAncestorJoinTest.java | 167 +++++++++++ ...inWhenCommonSecondaryTablePresentTest.java | 262 ++++++++++++++++++ ...storJoinWhenSecondaryTablePresentTest.java | 107 +++++++ .../OmitAncestorTestCase.java | 48 ++++ .../test/joinwithoutancestor/SqlAsserts.java | 140 ++++++++++ 25 files changed, 1224 insertions(+), 61 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/ASTReferencedTablesPrinter.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/joinwithoutancestor/OmitAncestorJoinTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/joinwithoutancestor/OmitAncestorJoinWhenCommonSecondaryTablePresentTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/joinwithoutancestor/OmitAncestorJoinWhenSecondaryTablePresentTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/joinwithoutancestor/OmitAncestorTestCase.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/joinwithoutancestor/SqlAsserts.java 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 789d01c8a5f3..52cf2f8ce56e 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.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 SessionFactoryOptionsBuilder(StandardServiceRegistry serviceRegistry, Boo 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 boolean isEnhancementAsProxyEnabled() { return enhancementAsProxyEnabled; } + @Override + public boolean isOmitJoinOfSuperclassTablesEnabled() { + return omitJoinOfSuperclassTablesEnabled; + } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // In-flight mutation access 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 1df0a779ae18..a3de3a79630e 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 boolean areJPACallbacksEnabled() { 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 044ab8c94c24..be055237b347 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 @@ default boolean areJPACallbacksEnabled() { default boolean isEnhancementAsProxyEnabled() { return false; } + + boolean isOmitJoinOfSuperclassTablesEnabled(); } 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 21852897e862..140ee68ed02a 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/engine/internal/JoinSequence.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/JoinSequence.java index bf2d699ea818..83c6d1cbddc7 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 @@ private void addSubclassJoins( 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 boolean isThetaStyle() { 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/hql/internal/ast/tree/AbstractMapComponentNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/AbstractMapComponentNode.java index 1daa679fc278..48ee9f35e2ad 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 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 @@ protected MapKeyEntityFromElement findOrAddMapKeyEntityFromElement(QueryableColl 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 77c23aaa42c0..7b70a52b9e87 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 static enum DereferenceType { * 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 void resolveFirstChild() throws SemanticException { // 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 Type getDataType() { 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 String getPropertyPath() { 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 8484c999e6d8..960a7d073e3f 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 @@ -517,6 +517,10 @@ public Type getPropertyType(String propertyName, String propertyPath) { 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 9fe8f365e48c..665733981ea9 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.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 @@ public QueryableCollection getQueryableCollection() { 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 7e44fabe97ba..8446af2aeefe 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 @@ protected boolean isFromElementUpdateOrDeleteRoot(FromElement element) { || 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/util/ASTPrinter.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/util/ASTPrinter.java index a9ed6538ca6e..43408a478f0a 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.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 @@ * @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 @@ private void showAst(ArrayList parents, PrintWriter pw, AST ast) { 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 @@ private void showAst(ArrayList parents, PrintWriter pw, AST ast) { } showNode( pw, ast ); + showNodeProperties( parents, pw, ast ); ArrayList newParents = new ArrayList( parents ); newParents.add( ast ); @@ -130,6 +125,17 @@ private void showAst(ArrayList parents, PrintWriter pw, AST ast) { 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 String nodeToString(AST ast) { 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 000000000000..1999881f1d4c --- /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 0196149407e2..f3361a8e5ebe 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 @@ 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.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.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 static JoinType toHibernateJoinType(int astJoinType) { } } + 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 void processJoins(QueryNode query) { 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 66a03adb3b7b..be777135154e 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/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index 8f41e64aa82b..9bd6ebb33333 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 @@ protected boolean[] getPropertySelectable() { 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, @@ -3890,7 +3898,8 @@ public String fromJoinFragment(String alias, boolean innerJoin, boolean includeS alias, innerJoin, includeSubclasses, - Collections.emptySet() + Collections.emptySet(), + null ).toFromFragmentString(); } @@ -3903,7 +3912,19 @@ public String fromJoinFragment( // 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 +3936,8 @@ public String whereJoinFragment(String alias, boolean innerJoin, boolean include alias, innerJoin, includeSubclasses, - Collections.emptySet() + Collections.emptySet(), + null ).toWhereFragmentString(); } @@ -3928,7 +3950,7 @@ public String whereJoinFragment( // 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 +3962,15 @@ protected JoinFragment createJoin( 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 +3981,8 @@ protected JoinFragment createJoin( j, innerJoin, includeSubclasses, - treatAsDeclarations + treatAsDeclarations, + referencedTables ); if ( joinType != null && joinType != JoinType.NONE ) { @@ -3971,8 +4003,28 @@ protected JoinType determineSubclassTableJoinType( 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 ); @@ -5735,6 +5787,15 @@ public String[][] getPolymorphicJoinColumns(String lhsTableAlias, String propert 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 2be118ad2c6c..aef89f331e6a 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 02b2f7b6f546..6930d7ffeabb 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 @@ private String[] getSubclassNameClosureBySubclassTable(int subclassTableNumber) return subclassNamesBySubclassTable[index]; } + @Override public String getPropertyTableName(String propertyName) { Integer index = getEntityMetamodel().getPropertyIndexOrNull( propertyName ); if ( index == null ) { @@ -1118,4 +1119,9 @@ public int determineTableNumberForColumn(String columnName) { 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 c6bd95ada1c2..2ab666d8c2f6 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 @@ protected boolean isNullableSubclassTable(int j) { 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 04f72dad9d1c..090d00a001a8 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 @@ protected boolean isClassOrSuperclassTable(int j) { return true; } + @Override public String getPropertyTableName(String propertyName) { //TODO: check this.... return getTableName(); @@ -483,4 +484,5 @@ public String[][] getContraintOrderedTableKeyColumnClosure() { public FilterAliasGenerator getFilterAliasGenerator(String rootAlias) { return new StaticFilterAliasGenerator( rootAlias ); } + } 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 1e8458764326..4d256a378f64 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 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 void testInvalidCollectionDereferencesFail() { 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 @@ private void assertInExist( String message, boolean expected, QueryTranslatorImp 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 void testManyToAnyReferences() { 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 void testCollectionMemberDeclarations(){ // 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 @@ private void check( @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 void testImplicitJoinsInGroupBy() { @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 void testConcatenation() { || 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 void testOrderBy() throws Exception { @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 void testImplicitJoins() throws Exception { @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 void testSelectProperty() throws Exception { @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 void testCorrelatedSubselect2() throws Exception { @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 void testClassName() throws Exception { @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 void testComponent2() throws Exception { @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 void testUnknownFailureFromMultiTableTest() { @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 void testOneToManyElementFunctionInWhere() throws Exception { @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 void testComponentNoAlias() throws Exception { 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 000000000000..fee912f180df --- /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 000000000000..70f284e02d11 --- /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 000000000000..0a5c8a57cb07 --- /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 000000000000..5c6d31028707 --- /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 000000000000..5932c9935209 --- /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(); + } + } +} From 19ac013eeb666a45066b6adb23c05df80a0fe6bb Mon Sep 17 00:00:00 2001 From: Carsten Hammer Date: Sun, 18 Aug 2019 12:25:07 +0200 Subject: [PATCH 42/99] HHH-13591 Replaces simple uses of array iteration with a corresponding for-each loop --- .../internal/bytebuddy/CodeTemplates.java | 36 +++++++++---------- .../java/org/hibernate/cfg/BinderHelper.java | 14 ++++---- .../cfg/ExternalSessionFactoryConfig.java | 4 +-- .../cfg/IndexOrUniqueKeySecondPass.java | 4 +-- .../engine/query/spi/HQLQueryPlan.java | 4 +-- .../engine/spi/NamedSQLQueryDefinition.java | 4 +-- .../hql/internal/ast/tree/FromElement.java | 11 +++--- .../hql/internal/ast/tree/IntoClause.java | 8 ++--- .../spi/id/TableBasedUpdateHandlerImpl.java | 4 +-- .../cte/CteValuesListUpdateHandlerImpl.java | 5 ++- .../AbstractInlineIdsUpdateHandlerImpl.java | 5 ++- .../entity/AbstractEntityPersister.java | 4 +-- .../main/java/org/hibernate/sql/Insert.java | 4 +-- .../java/org/hibernate/sql/InsertSelect.java | 4 +-- .../org/hibernate/sql/QueryJoinFragment.java | 8 ++--- ...tionExtractorJdbcDatabaseMetaDataImpl.java | 4 +-- .../tuple/entity/EntityMetamodel.java | 4 +-- .../org/hibernate/test/legacy/FumTest.java | 4 +-- .../ImplicitCompositeKeyJoinTest.java | 4 +-- ...SubselectFetchCollectionFromBatchTest.java | 24 ++++++------- .../testing/junit4/ExtraAssertions.java | 3 +- 21 files changed, 77 insertions(+), 85 deletions(-) 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 da3fdc33a70d..66639b27d45b 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 @@ -358,9 +358,9 @@ static class OneToManyOnCollectionHandler { 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 @@ static void enter(@FieldValue Collection field, @Advice.Argument(0) Collectio 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 @@ static class OneToManyOnMapHandler { 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 @@ static void enter(@FieldValue Map field, @Advice.Argument(0) Map arg 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 @@ static class ManyToManyHandler { 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 @@ static void enter(@Advice.This Object self, @FieldValue Collection field, @Ad 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/cfg/BinderHelper.java b/hibernate-core/src/main/java/org/hibernate/cfg/BinderHelper.java index 8147bd8ed583..21626d409cc3 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 @@ static PropertyData getPropertyOverriddenByMapperOrMapsId( 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 6cb581506159..b4ba00182fd2 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 @@ protected final Configuration buildConfiguration() { 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 6bdc9edef3bf..c1a86d1f8862 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 IndexOrUniqueKeySecondPass(String indexName, Ejb3Column column, MetadataB @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/engine/query/spi/HQLQueryPlan.java b/hibernate-core/src/main/java/org/hibernate/engine/query/spi/HQLQueryPlan.java index 6b0b954c2138..c142691fe57a 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 Set getEnabledFilterNames() { */ 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/NamedSQLQueryDefinition.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/NamedSQLQueryDefinition.java index 42ea03bb1e06..2210cae61752 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 void addQueryReturns(NativeSQLQueryReturn[] queryReturnsToAdd) { 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/hql/internal/ast/tree/FromElement.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/FromElement.java index 960a7d073e3f..007ae1b5b621 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 String[] getIdentityColumns() { 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()] ); 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 65a82d1724e2..2e34fab7d7a1 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 @@ private void visitPropertySpecNodes(AST propertyNode, List types) { 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 @@ else if ( name.equals( persister.getIdentifierPropertyName() ) ) { } 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/spi/id/TableBasedUpdateHandlerImpl.java b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/TableBasedUpdateHandlerImpl.java index 63f88f40e4ae..6f9706cf1889 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,8 @@ public int execute(SharedSessionContractImplementor session, QueryParameters que 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 532440edb813..544ac688d1a1 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,9 +112,8 @@ public int execute( } } if ( assignmentParameterSpecifications[i] != null ) { - 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); } } session 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 5086fb132db0..d4c08d45489b 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,9 +103,8 @@ public int execute( .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] - .bind( ps, queryParameters, session, position ); + for (ParameterSpecification assignmentParameterSpecification : assignmentParameterSpecifications[i]) { + position += assignmentParameterSpecification.bind(ps, queryParameters, session, position); } } jdbcCoordinator.getResultSetReturn() 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 9bd6ebb33333..11cfba373d17 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 @@ -5264,8 +5264,8 @@ private boolean isValueGenerationRequired(NonIdentifierAttribute attribute, Gene 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; } } 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 21bae46d6700..7dc47b70f914 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/Insert.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/Insert.java @@ -42,8 +42,8 @@ public Insert addColumn(String columnName) { } public Insert addColumns(String[] columnNames) { - for ( int i=0; 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; @@ -250,19 +250,19 @@ public void testMultiSubselectFetchSamePersisterQueryList() { 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; 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 491356db208f..7e3b495dd3b5 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 @@ private static synchronized Map jdbcTypeCodeMap() { 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() ); From 4661efa468c11b63e992a1c7a84c959c1477033b Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Tue, 20 Aug 2019 08:41:00 +0100 Subject: [PATCH 43/99] HHH-13591 Fixing formatting of previous patch --- .../internal/bytebuddy/CodeTemplates.java | 40 +++++++++---------- .../java/org/hibernate/cfg/BinderHelper.java | 12 +++--- .../cfg/ExternalSessionFactoryConfig.java | 4 +- .../cfg/IndexOrUniqueKeySecondPass.java | 4 +- .../engine/query/spi/HQLQueryPlan.java | 4 +- .../engine/spi/NamedSQLQueryDefinition.java | 2 +- .../hql/internal/ast/tree/FromElement.java | 8 ++-- .../hql/internal/ast/tree/IntoClause.java | 6 +-- .../spi/id/TableBasedUpdateHandlerImpl.java | 5 ++- .../cte/CteValuesListUpdateHandlerImpl.java | 5 ++- .../AbstractInlineIdsUpdateHandlerImpl.java | 5 ++- .../entity/AbstractEntityPersister.java | 4 +- .../main/java/org/hibernate/sql/Insert.java | 10 ++--- .../java/org/hibernate/sql/InsertSelect.java | 4 +- .../org/hibernate/sql/QueryJoinFragment.java | 8 ++-- ...tionExtractorJdbcDatabaseMetaDataImpl.java | 4 +- .../tuple/entity/EntityMetamodel.java | 4 +- .../org/hibernate/test/legacy/FumTest.java | 32 +++++++-------- .../ImplicitCompositeKeyJoinTest.java | 13 +++--- ...SubselectFetchCollectionFromBatchTest.java | 32 +++++++-------- .../testing/junit4/ExtraAssertions.java | 2 +- 21 files changed, 105 insertions(+), 103 deletions(-) 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 66639b27d45b..2c870b184474 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.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 @@ static class OneToManyOnCollectionHandler { 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 (Object array1 : array) { - if (argument == null || !argument.contains(array1)) { - setterNull(array1, null); + for ( Object array1 : array ) { + if ( argument == null || !argument.contains( array1 ) ) { + setterNull( array1, null ); } } } @@ -370,9 +370,9 @@ static void enter(@FieldValue Collection field, @Advice.Argument(0) Collectio 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 (Object array1 : array) { - if (Hibernate.isPropertyInitialized(array1, mappedBy) && getter(array1) != self) { - setterSelf(array1, self); + for ( Object array1 : array ) { + if ( Hibernate.isPropertyInitialized( array1, mappedBy ) && getter( array1 ) != self ) { + setterSelf( array1, self ); } } } @@ -399,9 +399,9 @@ static class OneToManyOnMapHandler { 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 (Object array1 : array) { - if (argument == null || !argument.values().contains(array1)) { - setterNull(array1, null); + for ( Object array1 : array ) { + if ( argument == null || !argument.values().contains( array1 ) ) { + setterNull( array1, null ); } } } @@ -411,9 +411,9 @@ static void enter(@FieldValue Map field, @Advice.Argument(0) Map arg 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 (Object array1 : array) { - if (Hibernate.isPropertyInitialized(array1, mappedBy) && getter(array1) != self) { - setterSelf(array1, self); + for ( Object array1 : array ) { + if ( Hibernate.isPropertyInitialized( array1, mappedBy ) && getter( array1 ) != self ) { + setterSelf( array1, self ); } } } @@ -467,9 +467,9 @@ static class ManyToManyHandler { 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 (Object array1 : array) { - if (argument == null || !argument.contains(array1)) { - getter(array1).remove(self); + for ( Object array1 : array ) { + if ( argument == null || !argument.contains( array1 ) ) { + getter( array1 ).remove( self ); } } } @@ -479,9 +479,9 @@ static void enter(@Advice.This Object self, @FieldValue Collection field, @Ad 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 (Object array1 : array) { - if (Hibernate.isPropertyInitialized(array1, mappedBy)) { - Collection c = getter(array1); + 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/cfg/BinderHelper.java b/hibernate-core/src/main/java/org/hibernate/cfg/BinderHelper.java index 21626d409cc3..3e7ea54c686c 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/BinderHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/BinderHelper.java @@ -1124,9 +1124,9 @@ static PropertyData getPropertyOverriddenByMapperOrMapsId( public static Map toAliasTableMap(SqlFragmentAlias[] aliases){ Map ret = new HashMap<>(); - for (SqlFragmentAlias aliase : aliases) { - if (StringHelper.isNotEmpty(aliase.table())) { - ret.put(aliase.alias(), aliase.table()); + for ( SqlFragmentAlias aliase : aliases ) { + if ( StringHelper.isNotEmpty( aliase.table() ) ) { + ret.put( aliase.alias(), aliase.table() ); } } return ret; @@ -1134,9 +1134,9 @@ public static Map toAliasTableMap(SqlFragmentAlias[] aliases){ public static Map toAliasEntityMap(SqlFragmentAlias[] aliases){ Map ret = new HashMap<>(); - for (SqlFragmentAlias aliase : aliases) { - if (aliase.entity() != void.class) { - ret.put(aliase.alias(), aliase.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 b4ba00182fd2..4121cb2bfe1c 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 @@ protected final Configuration buildConfiguration() { String[] mappingFiles = ConfigurationHelper.toStringArray( mapResources, " ,\n\t\r\f" ); - for (String mappingFile : mappingFiles) { - cfg.addResource(mappingFile); + 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 c1a86d1f8862..d5ae2c38b6b8 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 IndexOrUniqueKeySecondPass(String indexName, Ejb3Column column, MetadataB @Override public void doSecondPass(Map persistentClasses) throws MappingException { if ( columns != null ) { - for (String column1 : columns) { - addConstraintToColumn(column1); + for ( String column1 : columns ) { + addConstraintToColumn( column1 ); } } if ( column != null ) { 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 c142691fe57a..1ba9dbe6e036 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 Set getEnabledFilterNames() { */ public String[] getSqlStrings() { List sqlStrings = new ArrayList<>(); - for (QueryTranslator translator : translators) { - sqlStrings.addAll(translator.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/NamedSQLQueryDefinition.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/NamedSQLQueryDefinition.java index 2210cae61752..ea5fa3ecbbbe 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,7 +236,7 @@ public void addQueryReturns(NativeSQLQueryReturn[] queryReturnsToAdd) { allQueryReturns[i] = this.queryReturns[i]; } - for (NativeSQLQueryReturn queryReturnsToAdd1 : queryReturnsToAdd) { + for ( NativeSQLQueryReturn queryReturnsToAdd1 : queryReturnsToAdd ) { allQueryReturns[i] = queryReturnsToAdd1; i++; } 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 007ae1b5b621..6470c33d99ce 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,10 +343,10 @@ public String[] getIdentityColumns() { final String[] propertyNames = getIdentifierPropertyNames(); List columns = new ArrayList<>(); final boolean inSelect = getWalker().getStatementType() == HqlSqlTokenTypes.SELECT; - for (String propertyName : propertyNames) { - String[] propertyNameColumns = toColumns(table, propertyName, inSelect); - for (String propertyNameColumn : propertyNameColumns) { - columns.add(propertyNameColumn); + for ( String propertyName : propertyNames ) { + String[] propertyNameColumns = toColumns( table, propertyName, inSelect ); + for ( String propertyNameColumn : propertyNameColumns ) { + columns.add( propertyNameColumn ); } } return columns.toArray( new String[columns.size()] ); 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 2e34fab7d7a1..02c38142d49b 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 @@ private void visitPropertySpecNodes(AST propertyNode, List types) { if ( componentIds == null ) { String[] propertyNames = ( (CompositeType) persister.getIdentifierType() ).getPropertyNames(); componentIds = new HashSet(); - for (String propertyName : propertyNames) { - componentIds.add(propertyName); + for ( String propertyName : propertyNames ) { + componentIds.add( propertyName ); } } if ( componentIds.contains( name ) ) { @@ -194,7 +194,7 @@ else if ( name.equals( persister.getIdentifierPropertyName() ) ) { } private void renderColumns(String[] columnNames) { - for (String columnName : columnNames) { + for ( String columnName : columnNames ) { columnSpec += columnName + ", "; } } 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 6f9706cf1889..6b47360d0480 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 int execute(SharedSessionContractImplementor session, QueryParameters que ps = session.getJdbcCoordinator().getStatementPreparer().prepareStatement( updates[i], false ); if ( assignmentParameterSpecifications[i] != null ) { int position = 1; // jdbc params are 1-based - for (ParameterSpecification assignmentParameterSpecification : assignmentParameterSpecifications[i]) { - position += assignmentParameterSpecification.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 544ac688d1a1..7fcc81323319 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,9 @@ public int execute( } } if ( assignmentParameterSpecifications[i] != null ) { - for (ParameterSpecification assignmentParameterSpecification : assignmentParameterSpecifications[i]) { - position += assignmentParameterSpecification.bind(ps, queryParameters, session, position); + for ( ParameterSpecification assignmentParameterSpecification : assignmentParameterSpecifications[i] ) { + position += assignmentParameterSpecification + .bind( ps, queryParameters, session, position ); } } session 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 d4c08d45489b..8db4752a961b 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,9 @@ public int execute( .prepareStatement( update, false )) { int position = 1; // jdbc params are 1-based if ( assignmentParameterSpecifications[i] != null ) { - for (ParameterSpecification assignmentParameterSpecification : assignmentParameterSpecifications[i]) { - position += assignmentParameterSpecification.bind(ps, queryParameters, session, position); + for ( ParameterSpecification assignmentParameterSpecification : assignmentParameterSpecifications[i] ) { + position += assignmentParameterSpecification + .bind( ps, queryParameters, session, position ); } } jdbcCoordinator.getResultSetReturn() 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 11cfba373d17..d2b78ee95fb2 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 @@ -5264,8 +5264,8 @@ private boolean isValueGenerationRequired(NonIdentifierAttribute attribute, Gene if ( attribute.getType() instanceof ComponentType ) { final ComponentType type = (ComponentType) attribute.getType(); final ValueGeneration[] propertyValueGenerationStrategies = type.getPropertyValueGenerationStrategies(); - for (ValueGeneration propertyValueGenerationStrategie : propertyValueGenerationStrategies) { - if (isReadRequired(propertyValueGenerationStrategie, matchTiming)) { + for ( ValueGeneration propertyValueGenerationStrategie : propertyValueGenerationStrategies ) { + if ( isReadRequired( propertyValueGenerationStrategie, matchTiming ) ) { return true; } } 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 7dc47b70f914..25b2d7d276f0 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 Insert setComment(String comment) { } public Insert addColumn(String columnName) { - return addColumn(columnName, "?"); + return addColumn( columnName, "?" ); } public Insert addColumns(String[] columnNames) { - for (String columnName : columnNames) { - addColumn(columnName); + for ( String columnName : columnNames ) { + addColumn( columnName ); } return this; } @@ -67,12 +67,12 @@ public Insert addColumns(String[] columnNames, boolean[] insertable, String[] va } public Insert addColumn(String columnName, String valueExpression) { - columns.put(columnName, valueExpression); + columns.put( columnName, valueExpression ); return this; } public Insert addColumn(String columnName, Object value, LiteralType type) throws Exception { - return addColumn( columnName, type.objectToSQLString(value, dialect) ); + return addColumn( columnName, type.objectToSQLString( value, dialect ) ); } public Insert addIdentityColumn(String columnName) { diff --git a/hibernate-core/src/main/java/org/hibernate/sql/InsertSelect.java b/hibernate-core/src/main/java/org/hibernate/sql/InsertSelect.java index f242d2872aee..efc4a7cf4958 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/InsertSelect.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/InsertSelect.java @@ -44,8 +44,8 @@ public InsertSelect addColumn(String columnName) { } public InsertSelect addColumns(String[] columnNames) { - for (String columnName : columnNames) { - this.columnNames.add(columnName); + for ( String columnName : columnNames ) { + this.columnNames.add( columnName ); } return this; } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/QueryJoinFragment.java b/hibernate-core/src/main/java/org/hibernate/sql/QueryJoinFragment.java index 093465f0495c..2909f7cd7c05 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/QueryJoinFragment.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/QueryJoinFragment.java @@ -88,10 +88,12 @@ public JoinFragment copy() { } public void addCondition(String alias, String[] columns, String condition) { - for (String column : columns) { + for ( String column : columns ) { afterWhere.append( " and " ) - .append( alias ) - .append( '.' ).append(column).append(condition); + .append( alias ) + .append( '.' ) + .append( column ) + .append( condition ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/internal/InformationExtractorJdbcDatabaseMetaDataImpl.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/internal/InformationExtractorJdbcDatabaseMetaDataImpl.java index 562ec664a189..1a4bf472cb29 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/internal/InformationExtractorJdbcDatabaseMetaDataImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/internal/InformationExtractorJdbcDatabaseMetaDataImpl.java @@ -530,8 +530,8 @@ protected boolean isPhysicalTableType(String tableType) { if ( "TABLE".equalsIgnoreCase( tableType ) ) { return true; } - for (String extraPhysicalTableType : extraPhysicalTableTypes) { - if (extraPhysicalTableType.equalsIgnoreCase(tableType)) { + for ( String extraPhysicalTableType : extraPhysicalTableTypes ) { + if ( extraPhysicalTableType.equalsIgnoreCase( tableType ) ) { return true; } } diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java index 4c792b84c07a..0e4de4531e1e 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 @@ -832,8 +832,8 @@ private boolean indicatesCollection(Type type) { } else if ( type.isComponentType() ) { Type[] subtypes = ( (CompositeType) type ).getSubtypes(); - for (Type subtype : subtypes) { - if (indicatesCollection(subtype)) { + for ( Type subtype : subtypes ) { + if ( indicatesCollection( subtype ) ) { return true; } } 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 e267275f95bc..7fffc1a26757 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.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.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 void testCompositeIDQuery() throws Exception { ); Query qu = s.createQuery("select fum.fum, fum , fum.fum from Fum fum"); Type[] types = qu.getReturnTypes(); - assertTrue(types.length==3); - for (Type type : types) { - assertTrue(type != null); + assertTrue( types.length == 3 ); + for ( Type type : types ) { + assertTrue( type != null ); } - assertTrue(types[0] instanceof StringType); - assertTrue(types[1] instanceof EntityType); - assertTrue(types[2] instanceof StringType); + assertTrue( types[0] instanceof StringType ); + assertTrue( types[1] instanceof EntityType ); + assertTrue( types[2] instanceof StringType ); Iterator iter = qu.iterate(); int j = 0; while ( iter.hasNext() ) { 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 c24e3658c71a..4f59647bebbf 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.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 void testSchemaCreationSQLCommandIsGeneratedWithTheCorrectColumnSizeValue if ( command.toLowerCase().matches( "^create( (column|row))? table employee.+" ) ) { final String[] columnsDefinition = getColumnsDefinition( command ); - for (String columnsDefinition1 : columnsDefinition) { - checkColumnSize(columnsDefinition1); + for ( String columnsDefinition1 : columnsDefinition ) { + checkColumnSize( columnsDefinition1 ); } createTableEmployeeFound = true; } 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 9f2c8b3ac15a..82e3cdca7a1d 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.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.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 void testSubselectFetchFromEntityBatch() { assertEquals( 0, sessionFactory().getStatistics().getPrepareStatementCount() ); - for (EmployeeGroup group : groups) { + 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(group); - assertTrue(Hibernate.isInitialized(group)); + Hibernate.initialize( group ); + assertTrue( Hibernate.isInitialized( group ) ); // the collections should be uninitialized - assertFalse(Hibernate.isInitialized(group.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 void testMultiSubselectFetchSamePersisterQueryList() { assertEquals( 0, sessionFactory().getStatistics().getPrepareStatementCount() ); - for (EmployeeGroup group : groups) { + 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(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())); + 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(group.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-testing/src/main/java/org/hibernate/testing/junit4/ExtraAssertions.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit4/ExtraAssertions.java index 7e3b495dd3b5..d8bbf697c536 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,7 +73,7 @@ private static synchronized Map jdbcTypeCodeMap() { private static Map generateJdbcTypeCache() { final Field[] fields = Types.class.getFields(); Map cache = new HashMap( (int)( fields.length * .75 ) + 1 ); - for (Field field : fields) { + for ( Field field : fields ) { if ( Modifier.isStatic( field.getModifiers() ) ) { try { cache.put( field.get( null ), field.getName() ); From 408275ddecc3ba79bfddbe78a2867116c52e6423 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Tue, 20 Aug 2019 18:53:09 +0100 Subject: [PATCH 44/99] HHH-13594 ResourceRegistryStandardImpl#release could avoid allocating a capturing lambda --- .../internal/ResourceRegistryStandardImpl.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) 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 a40d738edaaa..492a53c811b8 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 @@ -146,13 +146,18 @@ public void release(ResultSet resultSet, Statement statement) { close( resultSet ); } - protected void closeAll(final HashMap resultSets) { + 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 { @@ -315,12 +320,7 @@ public void releaseResources() { jdbcObserver.jdbcReleaseRegistryResourcesStart(); } - xref.forEach( - (Statement s, HashMap r) -> { - closeAll( r ); - close( s ); - } - ); + xref.forEach( ResourceRegistryStandardImpl::releaseXref ); xref.clear(); closeAll( unassociatedResultSets ); From 5eaa1498df0f8f5f7f00515fb9bb2960b2ac489b Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Fri, 9 Aug 2019 10:28:18 +0100 Subject: [PATCH 45/99] HHH-13565 Session opening efficiency: introduce FastSessionServices and design for shared services among sessions --- .../AbstractSharedSessionContract.java | 3 + .../internal/FastSessionServices.java | 171 ++++++++++++++++++ .../internal/SessionFactoryImpl.java | 12 ++ .../org/hibernate/internal/SessionImpl.java | 62 +++---- 4 files changed, 209 insertions(+), 39 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java 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 bf37e43d5541..4cc33fa7cae1 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java @@ -116,6 +116,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; @@ -154,6 +155,7 @@ 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(); @@ -1226,6 +1228,7 @@ private void readObject(ObjectInputStream ois) throws IOException, ClassNotFound // -- see above factory = SessionFactoryImpl.deserialize( ois ); + fastSessionServices = factory.getFastSessionServices(); jdbcSessionContext = new JdbcSessionContextImpl( this, (StatementInspector) ois.readObject() ); jdbcCoordinator = JdbcCoordinatorImpl.deserialize( ois, this ); 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 000000000000..020d05c7e1c5 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java @@ -0,0 +1,171 @@ +/* + * 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.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.service.spi.ServiceRegistryImplementor; + +import java.util.Objects; + +/** + * 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 { + + private final Iterable clearEventListeners; + private final Iterable saveUpdateEventListeners; + private final Iterable evictEventListeners; + private final Iterable saveEventListeners; + private final Iterable updateEventListeners; + private final Iterable lockEventListeners; + private final Iterable persistEventListeners; + private final Iterable persistOnFlushEventListeners; + private final Iterable mergeEventListeners; + private final Iterable deleteEventListeners; + private final Iterable loadEventListeners; + private final Iterable refreshEventListeners; + private final Iterable replicateEventListeners; + private final Iterable autoFlushEventListeners; + private final Iterable dirtyCheckEventListeners; + private final Iterable flushEventListeners; + private final Iterable initCollectionEventListeners; + private final Iterable resolveNaturalIdEventListeners; + + FastSessionServices(SessionFactoryImpl sf) { + Objects.requireNonNull( sf ); + final ServiceRegistryImplementor sr = sf.getServiceRegistry(); + final EventListenerRegistry eventListenerRegistry = sr.getService( EventListenerRegistry.class ); + this.clearEventListeners = listeners( eventListenerRegistry, EventType.CLEAR ); + this.saveUpdateEventListeners = listeners( eventListenerRegistry, EventType.SAVE_UPDATE ); + this.evictEventListeners = listeners( eventListenerRegistry, EventType.EVICT ); + this.saveEventListeners = listeners( eventListenerRegistry, EventType.SAVE ); + this.updateEventListeners = listeners( eventListenerRegistry, EventType.UPDATE ); + this.lockEventListeners = listeners( eventListenerRegistry, EventType.LOCK ); + this.persistEventListeners = listeners( eventListenerRegistry, EventType.PERSIST ); + this.persistOnFlushEventListeners = listeners( eventListenerRegistry, EventType.PERSIST_ONFLUSH ); + this.mergeEventListeners = listeners( eventListenerRegistry, EventType.MERGE ); + this.deleteEventListeners = listeners( eventListenerRegistry, EventType.DELETE ); + this.loadEventListeners = listeners( eventListenerRegistry, EventType.LOAD ); + this.refreshEventListeners = listeners( eventListenerRegistry, EventType.REFRESH ); + this.replicateEventListeners = listeners( eventListenerRegistry, EventType.REPLICATE ); + this.autoFlushEventListeners = listeners( eventListenerRegistry, EventType.AUTO_FLUSH ); + this.dirtyCheckEventListeners = listeners( eventListenerRegistry, EventType.DIRTY_CHECK ); + this.flushEventListeners = listeners( eventListenerRegistry, EventType.FLUSH ); + this.initCollectionEventListeners = listeners( eventListenerRegistry, EventType.INIT_COLLECTION ); + this.resolveNaturalIdEventListeners = listeners( eventListenerRegistry, EventType.RESOLVE_NATURAL_ID ); + } + + Iterable getClearEventListeners() { + return clearEventListeners; + } + + Iterable getEvictEventListeners() { + return this.evictEventListeners; + } + + Iterable getDirtyCheckEventListeners() { + return this.dirtyCheckEventListeners; + } + + Iterable getSaveUpdateEventListeners() { + return this.saveUpdateEventListeners; + } + + Iterable getSaveEventListeners() { + return this.saveEventListeners; + } + + Iterable getUpdateEventListeners() { + return this.updateEventListeners; + } + + Iterable getLockEventListeners() { + return this.lockEventListeners; + } + + Iterable getPersistEventListeners() { + return persistEventListeners; + } + + Iterable getFlushEventListeners() { + return flushEventListeners; + } + + Iterable getPersistOnFlushEventListeners() { + return persistOnFlushEventListeners; + } + + Iterable getInitCollectionEventListeners() { + return initCollectionEventListeners; + } + + Iterable getLoadEventListeners() { + return loadEventListeners; + } + + Iterable getMergeEventListeners() { + return mergeEventListeners; + } + + Iterable getRefreshEventListeners() { + return refreshEventListeners; + } + + Iterable getDeleteEventListeners() { + return deleteEventListeners; + } + + Iterable getAutoFlushEventListeners() { + return autoFlushEventListeners; + } + + Iterable getReplicateEventListeners() { + return replicateEventListeners; + } + + Iterable getResolveNaturalIdEventListeners() { + return resolveNaturalIdEventListeners; + } + + private static Iterable listeners(EventListenerRegistry elr, EventType type) { + return elr.getEventListenerGroup( type ).listeners(); + } + +} 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 d311b4d18ad7..5af883fcf459 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java @@ -197,6 +197,8 @@ public final class SessionFactoryImpl implements SessionFactoryImplementor { private final transient TypeHelper typeHelper; + private final transient FastSessionServices fastSessionServices; + public SessionFactoryImpl( final MetadataImplementor metadata, SessionFactoryOptions options) { @@ -375,6 +377,8 @@ public void sessionFactoryClosed(SessionFactory factory) { fetchProfiles.put( fetchProfile.getName(), fetchProfile ); } + this.fastSessionServices = new FastSessionServices( this ); + this.observer.sessionFactoryCreated( this ); SessionFactoryRegistry.INSTANCE.addSessionFactory( @@ -1645,4 +1649,12 @@ private void logIfEmptyCompositesEnabled(Map props ) { LOG.emptyCompositesEnabled(); } } + + /** + * @return the FastSessionServices for this SessionFactory. + */ + FastSessionServices getFastSessionServices() { + return this.fastSessionServices; + } + } 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 f7b17e2bcb22..f2e1e0db2c3e 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.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.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; @@ -234,7 +231,6 @@ public final class SessionImpl private transient boolean discardOnClose; private transient TransactionObserver transactionObserver; - private transient EventListenerRegistry eventListenerRegistry; public SessionImpl(SessionFactoryImpl factory, SessionCreationOptions options) { super( factory, options ); @@ -355,7 +351,7 @@ private void internalClear() { actionQueue.clear(); final ClearEvent event = new ClearEvent( this ); - for ( ClearEventListener listener : listeners( EventType.CLEAR ) ) { + for ( ClearEventListener listener : fastSessionServices.getClearEventListeners() ) { listener.onClear( event ); } } @@ -633,24 +629,12 @@ private void fireSaveOrUpdate(SaveOrUpdateEvent event) { checkOpen(); checkTransactionSynchStatus(); checkNoUnresolvedActionsBeforeOperation(); - for ( SaveOrUpdateEventListener listener : listeners( EventType.SAVE_UPDATE ) ) { + for ( SaveOrUpdateEventListener listener : fastSessionServices.getSaveUpdateEventListeners() ) { listener.onSaveOrUpdate( event ); } 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 @@ -667,7 +651,7 @@ private Serializable fireSave(SaveOrUpdateEvent event) { checkOpen(); checkTransactionSynchStatus(); checkNoUnresolvedActionsBeforeOperation(); - for ( SaveOrUpdateEventListener listener : listeners( EventType.SAVE ) ) { + for ( SaveOrUpdateEventListener listener : fastSessionServices.getSaveEventListeners() ) { listener.onSaveOrUpdate( event ); } checkNoUnresolvedActionsAfterOperation(); @@ -691,7 +675,7 @@ private void fireUpdate(SaveOrUpdateEvent event) { checkOpen(); checkTransactionSynchStatus(); checkNoUnresolvedActionsBeforeOperation(); - for ( SaveOrUpdateEventListener listener : listeners( EventType.UPDATE ) ) { + for ( SaveOrUpdateEventListener listener : fastSessionServices.getUpdateEventListeners() ) { listener.onSaveOrUpdate( event ); } checkNoUnresolvedActionsAfterOperation(); @@ -726,7 +710,7 @@ private void fireLock(Object object, LockOptions options) { private void fireLock(LockEvent event) { checkOpen(); pulseTransactionCoordinator(); - for ( LockEventListener listener : listeners( EventType.LOCK ) ) { + for ( LockEventListener listener : fastSessionServices.getLockEventListeners() ) { listener.onLock( event ); } delayedAfterCompletion(); @@ -758,7 +742,7 @@ private void firePersist(PersistEvent event) { checkTransactionSynchStatus(); checkNoUnresolvedActionsBeforeOperation(); - for ( PersistEventListener listener : listeners( EventType.PERSIST ) ) { + for ( PersistEventListener listener : fastSessionServices.getPersistEventListeners() ) { listener.onPersist( event ); } } @@ -782,7 +766,7 @@ private void firePersist(Map copiedAlready, PersistEvent event) { pulseTransactionCoordinator(); try { - for ( PersistEventListener listener : listeners( EventType.PERSIST ) ) { + for ( PersistEventListener listener : fastSessionServices.getPersistEventListeners() ) { listener.onPersist( event, copiedAlready ); } } @@ -818,7 +802,7 @@ public void persistOnFlush(String entityName, Object object, Map copiedAlready) private void firePersistOnFlush(Map copiedAlready, PersistEvent event) { checkOpenOrWaitingForAutoClose(); pulseTransactionCoordinator(); - for ( PersistEventListener listener : listeners( EventType.PERSIST_ONFLUSH ) ) { + for ( PersistEventListener listener : fastSessionServices.getPersistOnFlushEventListeners() ) { listener.onPersist( event, copiedAlready ); } delayedAfterCompletion(); @@ -828,7 +812,7 @@ private void firePersistOnFlush(PersistEvent event) { checkOpen(); checkTransactionSynchStatus(); checkNoUnresolvedActionsBeforeOperation(); - for ( PersistEventListener listener : listeners( EventType.PERSIST_ONFLUSH ) ) { + for ( PersistEventListener listener : fastSessionServices.getPersistOnFlushEventListeners() ) { listener.onPersist( event ); } checkNoUnresolvedActionsAfterOperation(); @@ -859,7 +843,7 @@ private Object fireMerge(MergeEvent event) { try { checkTransactionSynchStatus(); checkNoUnresolvedActionsBeforeOperation(); - for ( MergeEventListener listener : listeners( EventType.MERGE ) ) { + for ( MergeEventListener listener : fastSessionServices.getMergeEventListeners() ) { listener.onMerge( event ); } checkNoUnresolvedActionsAfterOperation(); @@ -881,7 +865,7 @@ private Object fireMerge(MergeEvent event) { private void fireMerge(Map copiedAlready, MergeEvent event) { try { pulseTransactionCoordinator(); - for ( MergeEventListener listener : listeners( EventType.MERGE ) ) { + for ( MergeEventListener listener : fastSessionServices.getMergeEventListeners() ) { listener.onMerge( event, copiedAlready ); } } @@ -971,7 +955,7 @@ private void logRemoveOrphanBeforeUpdates(String timing, String entityName, Obje private void fireDelete(DeleteEvent event) { try{ pulseTransactionCoordinator(); - for ( DeleteEventListener listener : listeners( EventType.DELETE ) ) { + for ( DeleteEventListener listener : fastSessionServices.getDeleteEventListeners() ) { listener.onDelete( event ); } } @@ -993,7 +977,7 @@ private void fireDelete(DeleteEvent event) { private void fireDelete(DeleteEvent event, Set transientEntities) { try{ pulseTransactionCoordinator(); - for ( DeleteEventListener listener : listeners( EventType.DELETE ) ) { + for ( DeleteEventListener listener : fastSessionServices.getDeleteEventListeners() ) { listener.onDelete( event, transientEntities ); } } @@ -1270,7 +1254,7 @@ private void fireLoad(LoadEvent event, LoadType loadType) { // it seems they prevent these hot methods from being inlined. private void fireLoadNoChecks(LoadEvent event, LoadType loadType) { pulseTransactionCoordinator(); - for ( LoadEventListener listener : listeners( EventType.LOAD ) ) { + for ( LoadEventListener listener : fastSessionServices.getLoadEventListeners() ) { listener.onLoad( event, loadType ); } } @@ -1278,7 +1262,7 @@ private void fireLoadNoChecks(LoadEvent event, LoadType loadType) { private void fireResolveNaturalId(ResolveNaturalIdEvent event) { checkOpenOrWaitingForAutoClose(); pulseTransactionCoordinator(); - for ( ResolveNaturalIdEventListener listener : listeners( EventType.RESOLVE_NATURAL_ID ) ) { + for ( ResolveNaturalIdEventListener listener : fastSessionServices.getResolveNaturalIdEventListeners() ) { listener.onResolveNaturalId( event ); } delayedAfterCompletion(); @@ -1338,7 +1322,7 @@ private void fireRefresh(RefreshEvent event) { } } pulseTransactionCoordinator(); - for ( RefreshEventListener listener : listeners( EventType.REFRESH ) ) { + for ( RefreshEventListener listener : fastSessionServices.getRefreshEventListeners() ) { listener.onRefresh( event ); } } @@ -1359,7 +1343,7 @@ private void fireRefresh(RefreshEvent event) { private void fireRefresh(Map refreshedAlready, RefreshEvent event) { try { pulseTransactionCoordinator(); - for ( RefreshEventListener listener : listeners( EventType.REFRESH ) ) { + for ( RefreshEventListener listener : fastSessionServices.getRefreshEventListeners() ) { listener.onRefresh( event, refreshedAlready ); } } @@ -1388,7 +1372,7 @@ public void replicate(String entityName, Object obj, ReplicationMode replication private void fireReplicate(ReplicateEvent event) { checkOpen(); pulseTransactionCoordinator(); - for ( ReplicateEventListener listener : listeners( EventType.REPLICATE ) ) { + for ( ReplicateEventListener listener : fastSessionServices.getReplicateEventListeners() ) { listener.onReplicate( event ); } delayedAfterCompletion(); @@ -1409,7 +1393,7 @@ public void evict(Object object) throws HibernateException { private void fireEvict(EvictEvent event) { checkOpen(); pulseTransactionCoordinator(); - for ( EvictEventListener listener : listeners( EventType.EVICT ) ) { + for ( EvictEventListener listener : fastSessionServices.getEvictEventListeners() ) { listener.onEvict( event ); } delayedAfterCompletion(); @@ -1426,7 +1410,7 @@ protected boolean autoFlushIfRequired(Set querySpaces) throws HibernateException return false; } AutoFlushEvent event = new AutoFlushEvent( querySpaces, this ); - for ( AutoFlushEventListener listener : listeners( EventType.AUTO_FLUSH ) ) { + for ( AutoFlushEventListener listener : fastSessionServices.getAutoFlushEventListeners() ) { listener.onAutoFlush( event ); } return event.isFlushRequired(); @@ -1442,7 +1426,7 @@ public boolean isDirty() throws HibernateException { return true; } DirtyCheckEvent event = new DirtyCheckEvent( this ); - for ( DirtyCheckEventListener listener : listeners( EventType.DIRTY_CHECK ) ) { + for ( DirtyCheckEventListener listener : fastSessionServices.getDirtyCheckEventListeners() ) { listener.onDirtyCheck( event ); } delayedAfterCompletion(); @@ -1465,7 +1449,7 @@ private void doFlush() { } FlushEvent flushEvent = new FlushEvent( this ); - for ( FlushEventListener listener : listeners( EventType.FLUSH ) ) { + for ( FlushEventListener listener : fastSessionServices.getFlushEventListeners() ) { listener.onFlush( flushEvent ); } @@ -2269,7 +2253,7 @@ public void initializeCollection(PersistentCollection collection, boolean writin checkOpenOrWaitingForAutoClose(); pulseTransactionCoordinator(); InitializeCollectionEvent event = new InitializeCollectionEvent( collection, this ); - for ( InitializeCollectionEventListener listener : listeners( EventType.INIT_COLLECTION ) ) { + for ( InitializeCollectionEventListener listener : fastSessionServices.getInitCollectionEventListeners() ) { listener.onInitializeCollection( event ); } delayedAfterCompletion(); From a39acebde84e9d2a16d5b8472fc0b4430ed0bfbd Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Fri, 9 Aug 2019 12:55:28 +0100 Subject: [PATCH 46/99] HHH-13565 Move field AbstractSharedSessionContract#disallowOutOfTransactionUpdateOperations to constants in FastSessionServices --- .../internal/AbstractSharedSessionContract.java | 6 ++---- .../org/hibernate/internal/FastSessionServices.java | 10 ++++++++++ 2 files changed, 12 insertions(+), 4 deletions(-) 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 4cc33fa7cae1..ca2da2c541e7 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java @@ -139,7 +139,6 @@ 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(); @@ -157,7 +156,7 @@ public AbstractSharedSessionContract(SessionFactoryImpl factory, SessionCreation this.factory = factory; this.fastSessionServices = factory.getFastSessionServices(); this.cacheTransactionSync = factory.getCache().getRegionFactory().createTransactionContext( this ); - this.disallowOutOfTransactionUpdateOperations = !factory.getSessionFactoryOptions().isAllowOutOfTransactionUpdateOperations(); + this.flushMode = options.getInitialSessionFlushMode(); @@ -407,7 +406,7 @@ public boolean isTransactionInProgress() { @Override public void checkTransactionNeededForUpdateOperation(String exceptionMessage) { - if ( disallowOutOfTransactionUpdateOperations && !isTransactionInProgress() ) { + if ( fastSessionServices.disallowOutOfTransactionUpdateOperations && !isTransactionInProgress() ) { throw new TransactionRequiredException( exceptionMessage ); } } @@ -1239,7 +1238,6 @@ private void readObject(ObjectInputStream ois) throws IOException, ClassNotFound .buildTransactionCoordinator( jdbcCoordinator, this ); entityNameResolver = new CoordinatingEntityNameResolver( factory, interceptor ); - this.disallowOutOfTransactionUpdateOperations = !getFactory().getSessionFactoryOptions().isAllowOutOfTransactionUpdateOperations(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java b/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java index 020d05c7e1c5..4e2139a9bdfb 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java @@ -49,6 +49,8 @@ */ final class FastSessionServices { + // All session events need to be iterated frequently: + private final Iterable clearEventListeners; private final Iterable saveUpdateEventListeners; private final Iterable evictEventListeners; @@ -68,9 +70,14 @@ final class FastSessionServices { private final Iterable initCollectionEventListeners; private final Iterable resolveNaturalIdEventListeners; + //Intentionally Package private: + final boolean disallowOutOfTransactionUpdateOperations; + FastSessionServices(SessionFactoryImpl sf) { Objects.requireNonNull( sf ); final ServiceRegistryImplementor sr = sf.getServiceRegistry(); + + // Pre-compute all iterators on Event listeners: final EventListenerRegistry eventListenerRegistry = sr.getService( EventListenerRegistry.class ); this.clearEventListeners = listeners( eventListenerRegistry, EventType.CLEAR ); this.saveUpdateEventListeners = listeners( eventListenerRegistry, EventType.SAVE_UPDATE ); @@ -90,6 +97,9 @@ final class FastSessionServices { this.flushEventListeners = listeners( eventListenerRegistry, EventType.FLUSH ); this.initCollectionEventListeners = listeners( eventListenerRegistry, EventType.INIT_COLLECTION ); this.resolveNaturalIdEventListeners = listeners( eventListenerRegistry, EventType.RESOLVE_NATURAL_ID ); + + //Other highly useful constants: + this.disallowOutOfTransactionUpdateOperations = !sf.getSessionFactoryOptions().isAllowOutOfTransactionUpdateOperations(); } Iterable getClearEventListeners() { From 8a3c1ee5049c5a0c34836c35da6911d333393e8a Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Fri, 9 Aug 2019 13:00:49 +0100 Subject: [PATCH 47/99] HHH-13565 Promote field useStreamForLobBinding as cross-Session constant --- .../hibernate/internal/AbstractSharedSessionContract.java | 8 +------- .../java/org/hibernate/internal/FastSessionServices.java | 6 ++++++ 2 files changed, 7 insertions(+), 7 deletions(-) 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 ca2da2c541e7..0e878e8f7972 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java @@ -37,7 +37,6 @@ 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; @@ -143,7 +142,6 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont // transient & non-final for Serialization purposes - ugh private transient SessionEventListenerManagerImpl sessionEventsManager = new SessionEventListenerManagerImpl(); private transient EntityNameResolver entityNameResolver; - private transient Boolean useStreamForLobBinding; private Integer jdbcBatchSize; @@ -537,11 +535,7 @@ public EntityKey generateEntityKey(Serializable id, EntityPersister persister) { @Override public boolean useStreamForLobBinding() { - if ( useStreamForLobBinding == null ) { - useStreamForLobBinding = Environment.useStreamsForBinary() - || getJdbcServices().getJdbcEnvironment().getDialect().useInputStreamToInsertBlob(); - } - return useStreamForLobBinding; + return fastSessionServices.useStreamForLobBinding; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java b/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java index 4e2139a9bdfb..2fa489166ab7 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java @@ -6,6 +6,8 @@ */ package org.hibernate.internal; +import org.hibernate.cfg.Environment; +import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.event.service.spi.EventListenerRegistry; import org.hibernate.event.spi.AutoFlushEventListener; import org.hibernate.event.spi.ClearEventListener; @@ -72,10 +74,12 @@ final class FastSessionServices { //Intentionally Package private: final boolean disallowOutOfTransactionUpdateOperations; + final boolean useStreamForLobBinding; FastSessionServices(SessionFactoryImpl sf) { Objects.requireNonNull( sf ); final ServiceRegistryImplementor sr = sf.getServiceRegistry(); + final JdbcServices jdbcServices = sf.getJdbcServices(); // Pre-compute all iterators on Event listeners: final EventListenerRegistry eventListenerRegistry = sr.getService( EventListenerRegistry.class ); @@ -100,6 +104,8 @@ final class FastSessionServices { //Other highly useful constants: this.disallowOutOfTransactionUpdateOperations = !sf.getSessionFactoryOptions().isAllowOutOfTransactionUpdateOperations(); + this.useStreamForLobBinding = Environment.useStreamsForBinary() + || jdbcServices.getJdbcEnvironment().getDialect().useInputStreamToInsertBlob(); } Iterable getClearEventListeners() { From 72ce71ef1079d183c5f6d1b67c65f984f67d25b4 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Fri, 9 Aug 2019 13:05:17 +0100 Subject: [PATCH 48/99] HHH-13565 Dialect is another constant --- .../internal/AbstractSharedSessionContract.java | 9 +-------- .../hibernate/internal/FastSessionServices.java | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 9 deletions(-) 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 0e878e8f7972..3a92f8d5fd92 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java @@ -37,7 +37,6 @@ 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.dialect.Dialect; import org.hibernate.engine.ResultSetMappingDefinition; import org.hibernate.engine.internal.SessionEventListenerManagerImpl; import org.hibernate.engine.jdbc.LobCreationContext; @@ -562,13 +561,7 @@ public T execute(final LobCreationContext.Callback callback) { @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 diff --git a/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java b/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java index 2fa489166ab7..e7c7afe97daa 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java @@ -7,6 +7,7 @@ package org.hibernate.internal; import org.hibernate.cfg.Environment; +import org.hibernate.dialect.Dialect; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.event.service.spi.EventListenerRegistry; import org.hibernate.event.spi.AutoFlushEventListener; @@ -26,6 +27,7 @@ import org.hibernate.event.spi.ResolveNaturalIdEventListener; import org.hibernate.event.spi.SaveOrUpdateEventListener; import org.hibernate.service.spi.ServiceRegistryImplementor; +import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; import java.util.Objects; @@ -76,6 +78,8 @@ final class FastSessionServices { final boolean disallowOutOfTransactionUpdateOperations; final boolean useStreamForLobBinding; + private final Dialect dialect; + FastSessionServices(SessionFactoryImpl sf) { Objects.requireNonNull( sf ); final ServiceRegistryImplementor sr = sf.getServiceRegistry(); @@ -103,9 +107,10 @@ final class FastSessionServices { this.resolveNaturalIdEventListeners = listeners( eventListenerRegistry, EventType.RESOLVE_NATURAL_ID ); //Other highly useful constants: + this.dialect = jdbcServices.getJdbcEnvironment().getDialect(); this.disallowOutOfTransactionUpdateOperations = !sf.getSessionFactoryOptions().isAllowOutOfTransactionUpdateOperations(); this.useStreamForLobBinding = Environment.useStreamsForBinary() - || jdbcServices.getJdbcEnvironment().getDialect().useInputStreamToInsertBlob(); + || dialect.useInputStreamToInsertBlob(); } Iterable getClearEventListeners() { @@ -184,4 +189,13 @@ private static Iterable listeners(EventListenerRegistry elr, EventType return elr.getEventListenerGroup( type ).listeners(); } + SqlTypeDescriptor remapSqlTypeDescriptor(SqlTypeDescriptor sqlTypeDescriptor) { + if ( !sqlTypeDescriptor.canBeRemapped() ) { + return sqlTypeDescriptor; + } + + final SqlTypeDescriptor remapped = dialect.remapSqlTypeDescriptor( sqlTypeDescriptor ); + return remapped == null ? sqlTypeDescriptor : remapped; + } + } From 185ef2edb7cae1c74f0f92ba91b79ef2b35e2042 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Fri, 9 Aug 2019 13:19:11 +0100 Subject: [PATCH 49/99] HHH-13565 Extract some hot ServiceRegistry services as well --- .../internal/AbstractSharedSessionContract.java | 13 +++++-------- .../hibernate/internal/FastSessionServices.java | 17 +++++++++++++++++ 2 files changed, 22 insertions(+), 8 deletions(-) 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 3a92f8d5fd92..73842643601d 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java @@ -210,11 +210,8 @@ public AbstractSharedSessionContract(SessionFactoryImpl factory, SessionCreation 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.transactionCoordinator = fastSessionServices.transactionCoordinatorBuilder.buildTransactionCoordinator( jdbcCoordinator, this ); } } @@ -510,17 +507,17 @@ public boolean isConnected() { 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 ); } } @@ -964,7 +961,7 @@ else if ( namedQueryDefinition.getResultSetRef() != null ) { 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( diff --git a/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java b/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java index e7c7afe97daa..bdda279fd720 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java @@ -6,8 +6,11 @@ */ package org.hibernate.internal; +import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; 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.JdbcServices; import org.hibernate.event.service.spi.EventListenerRegistry; import org.hibernate.event.spi.AutoFlushEventListener; @@ -26,6 +29,7 @@ import org.hibernate.event.spi.ReplicateEventListener; import org.hibernate.event.spi.ResolveNaturalIdEventListener; import org.hibernate.event.spi.SaveOrUpdateEventListener; +import org.hibernate.resource.transaction.spi.TransactionCoordinatorBuilder; import org.hibernate.service.spi.ServiceRegistryImplementor; import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; @@ -77,7 +81,13 @@ final class FastSessionServices { //Intentionally Package private: final boolean disallowOutOfTransactionUpdateOperations; final boolean useStreamForLobBinding; + final boolean requiresMultiTenantConnectionProvider; + final ConnectionProvider connectionProvider; + final MultiTenantConnectionProvider multiTenantConnectionProvider; + final ClassLoaderService classLoaderService; + final TransactionCoordinatorBuilder transactionCoordinatorBuilder; + //Private fields: private final Dialect dialect; FastSessionServices(SessionFactoryImpl sf) { @@ -111,6 +121,13 @@ final class FastSessionServices { this.disallowOutOfTransactionUpdateOperations = !sf.getSessionFactoryOptions().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 ); } Iterable getClearEventListeners() { From 8931ef0962a026a9dad7dd72414137135bc3ce7e Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Mon, 12 Aug 2019 09:41:49 +0100 Subject: [PATCH 50/99] HHH-13565 Promote JDBCServices as hot service to be retrieved from FastSessionService as well --- .../jdbc/internal/JdbcCoordinatorImpl.java | 10 +++++----- .../internal/AbstractSharedSessionContract.java | 2 +- .../hibernate/internal/FastSessionServices.java | 2 ++ .../internal/LogicalConnectionManagedImpl.java | 17 ++++++++--------- .../jdbc/internal/JdbcCoordinatorTest.java | 5 ++--- 5 files changed, 18 insertions(+), 18 deletions(-) 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 0d5dd3876b95..0101585b9212 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 JdbcCoordinatorImpl( 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( 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 73842643601d..847105cbb67e 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java @@ -210,7 +210,7 @@ public AbstractSharedSessionContract(SessionFactoryImpl factory, SessionCreation else { this.isTransactionCoordinatorShared = false; this.autoJoinTransactions = options.shouldAutoJoinTransactions(); - this.jdbcCoordinator = new JdbcCoordinatorImpl( options.getConnection(), this ); + this.jdbcCoordinator = new JdbcCoordinatorImpl( options.getConnection(), this, fastSessionServices.jdbcServices ); this.transactionCoordinator = fastSessionServices.transactionCoordinatorBuilder.buildTransactionCoordinator( jdbcCoordinator, this ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java b/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java index bdda279fd720..7a42c4f82c75 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java @@ -86,6 +86,7 @@ final class FastSessionServices { final MultiTenantConnectionProvider multiTenantConnectionProvider; final ClassLoaderService classLoaderService; final TransactionCoordinatorBuilder transactionCoordinatorBuilder; + final JdbcServices jdbcServices; //Private fields: private final Dialect dialect; @@ -128,6 +129,7 @@ final class FastSessionServices { 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 ); } Iterable getClearEventListeners() { 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 18b5774ece51..2a4cb84d5db6 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,7 +91,9 @@ private LogicalConnectionManagedImpl( JdbcConnectionAccess jdbcConnectionAccess, JdbcSessionContext jdbcSessionContext, boolean closed) { - this( jdbcConnectionAccess, jdbcSessionContext, new ResourceRegistryStandardImpl() ); + this( jdbcConnectionAccess, jdbcSessionContext, new ResourceRegistryStandardImpl(), + jdbcSessionContext.getServiceRegistry().getService( JdbcServices.class ) + ); this.closed = closed; } @@ -221,7 +220,7 @@ public void serialize(ObjectOutputStream oos) throws IOException { 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/test/java/org/hibernate/engine/jdbc/internal/JdbcCoordinatorTest.java b/hibernate-core/src/test/java/org/hibernate/engine/jdbc/internal/JdbcCoordinatorTest.java index df1646aa58ae..50a833426583 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 void testConnectionClose() 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 void testConnectionClose() JdbcCoordinatorImpl jdbcCoordinator = new JdbcCoordinatorImpl( null, - sessionOwner + sessionOwner, + jdbcServices ); Batch currentBatch = Mockito.mock( Batch.class ); From be9fe275923c171390ee8ddd340b35a58e7baa73 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Mon, 12 Aug 2019 19:47:26 +0100 Subject: [PATCH 51/99] HHH-13565 Promote to FastSessionServices: isJtaTransactionAccessible --- .../AbstractSharedSessionContract.java | 13 +------------ .../internal/FastSessionServices.java | 19 ++++++++++++++++--- .../org/hibernate/internal/SessionImpl.java | 2 +- 3 files changed, 18 insertions(+), 16 deletions(-) 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 847105cbb67e..6f1d201cbaed 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java @@ -407,7 +407,7 @@ public void checkTransactionNeededForUpdateOperation(String 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" ); @@ -415,17 +415,6 @@ public Transaction getTransaction() throws HibernateException { 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 ) { diff --git a/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java b/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java index 7a42c4f82c75..d593245b32f8 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java @@ -87,6 +87,7 @@ final class FastSessionServices { final ClassLoaderService classLoaderService; final TransactionCoordinatorBuilder transactionCoordinatorBuilder; final JdbcServices jdbcServices; + final boolean isJtaTransactionAccessible; //Private fields: private final Dialect dialect; @@ -120,9 +121,8 @@ final class FastSessionServices { //Other highly useful constants: this.dialect = jdbcServices.getJdbcEnvironment().getDialect(); this.disallowOutOfTransactionUpdateOperations = !sf.getSessionFactoryOptions().isAllowOutOfTransactionUpdateOperations(); - this.useStreamForLobBinding = Environment.useStreamsForBinary() - || dialect.useInputStreamToInsertBlob(); - this.requiresMultiTenantConnectionProvider = sf.getSettings().getMultiTenancyStrategy().requiresMultiTenantConnectionProvider(); + this.useStreamForLobBinding = Environment.useStreamsForBinary() || dialect.useInputStreamToInsertBlob(); + this.requiresMultiTenantConnectionProvider = sf.getSettings().getMultiTenancyStrategy().requiresMultiTenantConnectionProvider(); //Some "hot" services: this.connectionProvider = requiresMultiTenantConnectionProvider ? null : sr.getService( ConnectionProvider.class ); @@ -130,6 +130,8 @@ final class FastSessionServices { this.classLoaderService = sr.getService( ClassLoaderService.class ); this.transactionCoordinatorBuilder = sr.getService( TransactionCoordinatorBuilder.class ); this.jdbcServices = sr.getService( JdbcServices.class ); + + this.isJtaTransactionAccessible = isTransactionAccessible( sf, transactionCoordinatorBuilder ); } Iterable getClearEventListeners() { @@ -217,4 +219,15 @@ SqlTypeDescriptor remapSqlTypeDescriptor(SqlTypeDescriptor 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; + } + } 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 f2e1e0db2c3e..82603aaf35b2 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -2493,7 +2493,7 @@ private Transaction getTransactionIfAccessible() { // 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 From bc5b3c028febed445fe1f1e70eaf874e41dc93cc Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Mon, 12 Aug 2019 10:20:33 +0100 Subject: [PATCH 52/99] HHH-13565 Remove some related dead code --- .../internal/StatefulPersistenceContext.java | 6 +---- .../internal/SessionFactoryImpl.java | 1 - .../org/hibernate/internal/SessionImpl.java | 25 ------------------- .../internal/AbstractLoadPlanBasedLoader.java | 9 ------- .../internal/ResultSetProcessorImpl.java | 4 --- 5 files changed, 1 insertion(+), 44 deletions(-) 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 dabcc510c611..93f7d9cc4bd6 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 @@ -101,7 +101,6 @@ public class StatefulPersistenceContext implements PersistenceContext { private Map entitiesByUniqueKey; private EntityEntryContext entityEntryContext; -// private Map entityEntries; // Entity proxies, by EntityKey private ConcurrentMap proxiesByKey; @@ -295,11 +294,8 @@ private void setHasNonReadOnlyEnties(Status status) { @Override public void afterTransactionCompletion() { cleanUpInsertedKeysAfterTransaction(); + // Downgrade locks entityEntryContext.downgradeLocks(); -// // Downgrade locks -// for ( EntityEntry o : entityEntries.values() ) { -// o.setLockMode( LockMode.NONE ); -// } } /** 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 5af883fcf459..af25ea4876ec 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java @@ -1160,7 +1160,6 @@ static class SessionBuilderImpl implements SessionBuil //todo : expose setting private SessionOwnerBehavior sessionOwnerBehavior = SessionOwnerBehavior.LEGACY_NATIVE; - private PersistenceUnitTransactionType persistenceUnitTransactionType; SessionBuilderImpl(SessionFactoryImpl sessionFactory) { this.sessionFactory = 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 82603aaf35b2..b17a51ebdc76 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -167,8 +167,6 @@ 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; @@ -456,10 +454,6 @@ protected void checkSessionFactoryOpen() { } } - private boolean isFlushModeNever() { - return FlushMode.isManualFlushMode( getHibernateFlushMode() ); - } - private void managedFlush() { if ( isClosed() && !waitingForAutoClose ) { log.trace( "Skipping auto-flush due to session closed" ); @@ -3353,25 +3347,6 @@ public boolean isFlushBeforeCompletionEnabled() { 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 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 eb5a52688a7a..eb320df4c7d2 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.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.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 int[] getNamedParameterLocations(String name) { } } - 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 886ce501496b..c710b2e01ccf 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 ResultSetProcessorImpl( this.hadSubselectFetches = hadSubselectFetches; } - public RowReader getRowReader() { - return rowReader; - } - @Override public ScrollableResultSetProcessor toOnDemandForm() { // todo : implement From 457e9b61faffc9f5d4fd60fb164ca6b0efa77b71 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Mon, 12 Aug 2019 20:28:25 +0100 Subject: [PATCH 53/99] HHH-13565 Formatting improvements --- .../AbstractSharedSessionContract.java | 8 +++---- .../internal/util/ConfigurationHelper.java | 22 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) 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 6f1d201cbaed..cd5932de2aea 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java @@ -423,7 +423,7 @@ public Transaction accessTransaction() { this ); } - if ( !isClosed() || (waitingForAutoClose && factory.isOpen()) ) { + if ( !isClosed() || ( waitingForAutoClose && factory.isOpen() ) ) { getTransactionCoordinator().pulse(); } return this.currentHibernateTransaction; @@ -902,7 +902,7 @@ protected QueryImplementor createQuery(NamedQueryDefinition namedQueryDef @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 ); } @@ -911,8 +911,8 @@ protected NativeQueryImplementor createNativeQuery(NamedSQLQueryDefinition q 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() ); 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 8a66a509c75b..ea78f317db7e 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 static void overrideProperties(Properties properties, Map overrides) 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 static FlushMode getFlushMode(Object value) { } 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 ); } } From e23e6a73e6a6b068573e4ab4044d40bb78e67e9f Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Mon, 12 Aug 2019 21:33:03 +0100 Subject: [PATCH 54/99] HHH-13565 Extract Session properties to avoid eager initialization of Map properties --- .../internal/FastSessionServices.java | 94 +++++++++++++++ .../org/hibernate/internal/SessionImpl.java | 113 +++++++++--------- 2 files changed, 150 insertions(+), 57 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java b/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java index d593245b32f8..af03aaca854c 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java @@ -6,6 +6,9 @@ */ 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.cfg.Environment; import org.hibernate.dialect.Dialect; @@ -29,12 +32,27 @@ 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.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.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. * @@ -57,6 +75,11 @@ */ final class FastSessionServices { + /** + * Default session properties + */ + final Map defaultSessionProperties; + // All session events need to be iterated frequently: private final Iterable clearEventListeners; @@ -88,9 +111,12 @@ final class FastSessionServices { final TransactionCoordinatorBuilder transactionCoordinatorBuilder; final JdbcServices jdbcServices; final boolean isJtaTransactionAccessible; + final CacheMode initialSessionCacheMode; //Private fields: private final Dialect dialect; + private final CacheStoreMode defaultCacheStoreMode; + private final CacheRetrieveMode defaultCacheRetrieveMode; FastSessionServices(SessionFactoryImpl sf) { Objects.requireNonNull( sf ); @@ -132,6 +158,11 @@ final class FastSessionServices { 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 ); } Iterable getClearEventListeners() { @@ -230,4 +261,67 @@ private static boolean isTransactionAccessible(SessionFactoryImpl sf, Transactio 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 ); + } } 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 b17a51ebdc76..c7d897b090c3 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -192,23 +192,15 @@ * @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; @@ -260,67 +252,38 @@ public SessionImpl(SessionFactoryImpl factory, SessionCreationOptions options) { statistics.openSession(); } + setLockOptions( this.properties == null ? fastSessionServices.defaultSessionProperties : this.properties, this.lockOptions ); + 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 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 ) ); - } - } - } - protected void applyQuerySettingsAndHints(Query query) { if ( lockOptions.getLockMode() != LockMode.NONE ) { query.setLockMode( getLockMode( lockOptions.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 @@ -3519,20 +3482,20 @@ private CacheMode determineAppropriateLocalCacheMode(Map localPr } 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 ); } @@ -3662,12 +3625,48 @@ public void setProperty(String propertyName, Object value) { 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 ) ) { + setLockOptions( properties, this.lockOptions ); + } + 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 ); } From 4ef8030f5163ff50ccb0c66309e5d2fffa587380 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Tue, 13 Aug 2019 10:15:59 +0100 Subject: [PATCH 55/99] HHH-13565 Move field SessionImpl#discardOnClose to FastSessionServices as well --- .../java/org/hibernate/internal/FastSessionServices.java | 2 ++ .../src/main/java/org/hibernate/internal/SessionImpl.java | 8 +------- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java b/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java index af03aaca854c..14f1e56d3211 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java @@ -112,6 +112,7 @@ final class FastSessionServices { final JdbcServices jdbcServices; final boolean isJtaTransactionAccessible; final CacheMode initialSessionCacheMode; + final boolean discardOnClose; //Private fields: private final Dialect dialect; @@ -163,6 +164,7 @@ final class FastSessionServices { this.defaultCacheStoreMode = determineCacheStoreMode( defaultSessionProperties ); this.defaultCacheRetrieveMode = determineCacheRetrieveMode( defaultSessionProperties ); this.initialSessionCacheMode = CacheModeHelper.interpretCacheMode( defaultCacheStoreMode, defaultCacheRetrieveMode ); + this.discardOnClose = sf.getSessionFactoryOptions().isReleaseResourcesOnCloseEnabled(); } Iterable getClearEventListeners() { 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 c7d897b090c3..004149dac0f9 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -218,8 +218,6 @@ public final class SessionImpl private transient LoadEvent loadEvent; //cached LoadEvent instance - private transient boolean discardOnClose; - private transient TransactionObserver transactionObserver; public SessionImpl(SessionFactoryImpl factory, SessionCreationOptions options) { @@ -232,8 +230,6 @@ public SessionImpl(SessionFactoryImpl factory, SessionCreationOptions options) { 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(); @@ -345,7 +341,7 @@ public void closeWithoutOpenChecks() throws HibernateException { // Original hibernate-entitymanager EM#close behavior checkSessionFactoryOpen(); checkOpenOrWaitingForAutoClose(); - if ( discardOnClose || !isTransactionInProgress( false ) ) { + if ( fastSessionServices.discardOnClose || !isTransactionInProgress( false ) ) { super.close(); } else { @@ -3889,7 +3885,5 @@ private void readObject(ObjectInputStream ois) throws IOException, ClassNotFound for ( String filterName : loadQueryInfluencers.getEnabledFilterNames() ) { ( (FilterImpl) loadQueryInfluencers.getEnabledFilter( filterName ) ).afterDeserialize( getFactory() ); } - - this.discardOnClose = getFactory().getSessionFactoryOptions().isReleaseResourcesOnCloseEnabled(); } } From 4b2f056a632c64b4bde2ad7e5fe05ace4d50e17c Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Tue, 13 Aug 2019 20:21:03 +0100 Subject: [PATCH 56/99] HHH-13565 Avoid allocating ArrayList of ConnectionObserver instances on each Session --- .../internal/AbstractSharedSessionContract.java | 7 ++----- .../org/hibernate/internal/FastSessionServices.java | 9 +++++++++ .../java/org/hibernate/internal/JdbcObserverImpl.java | 10 ++++------ .../org/hibernate/internal/JdbcSessionContextImpl.java | 7 +++++-- 4 files changed, 20 insertions(+), 13 deletions(-) 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 cd5932de2aea..5a90b841fdca 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java @@ -34,16 +34,13 @@ import org.hibernate.MultiTenancyStrategy; 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.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; @@ -173,7 +170,7 @@ public AbstractSharedSessionContract(SessionFactoryImpl factory, SessionCreation this.jdbcTimeZone = options.getJdbcTimeZone(); 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 ); @@ -1201,7 +1198,7 @@ private void readObject(ObjectInputStream ois) throws IOException, ClassNotFound factory = SessionFactoryImpl.deserialize( ois ); fastSessionServices = factory.getFastSessionServices(); - jdbcSessionContext = new JdbcSessionContextImpl( this, (StatementInspector) ois.readObject() ); + jdbcSessionContext = new JdbcSessionContextImpl( this, (StatementInspector) ois.readObject(), fastSessionServices ); jdbcCoordinator = JdbcCoordinatorImpl.deserialize( ois, this ); cacheTransactionSync = factory.getCache().getRegionFactory().createTransactionContext( this ); diff --git a/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java b/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java index 14f1e56d3211..c0e7a29d4811 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java @@ -14,6 +14,7 @@ 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.EventListenerRegistry; import org.hibernate.event.spi.AutoFlushEventListener; @@ -41,6 +42,7 @@ import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; @@ -118,6 +120,7 @@ final class FastSessionServices { private final Dialect dialect; private final CacheStoreMode defaultCacheStoreMode; private final CacheRetrieveMode defaultCacheRetrieveMode; + private List defaultJdbcObservers; FastSessionServices(SessionFactoryImpl sf) { Objects.requireNonNull( sf ); @@ -165,6 +168,7 @@ final class FastSessionServices { this.defaultCacheRetrieveMode = determineCacheRetrieveMode( defaultSessionProperties ); this.initialSessionCacheMode = CacheModeHelper.interpretCacheMode( defaultCacheStoreMode, defaultCacheRetrieveMode ); this.discardOnClose = sf.getSessionFactoryOptions().isReleaseResourcesOnCloseEnabled(); + this.defaultJdbcObservers = Collections.singletonList( new ConnectionObserverStatsBridge( sf ) ); } Iterable getClearEventListeners() { @@ -326,4 +330,9 @@ private static CacheRetrieveMode determineCacheRetrieveMode(Map private static CacheStoreMode determineCacheStoreMode(Map settings) { return ( CacheStoreMode ) settings.get( JPA_SHARED_CACHE_STORE_MODE ); } + + public Iterable getDefaultJdbcObservers() { + return defaultJdbcObservers; + } + } 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 ef02f759a4a9..be3cf12978e3 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/JdbcObserverImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/JdbcObserverImpl.java @@ -7,8 +7,6 @@ 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.SharedSessionContractImplementor; @@ -18,13 +16,13 @@ * @author Steve Ebersole */ public class JdbcObserverImpl implements JdbcObserver { + private final SharedSessionContractImplementor session; - private final transient List observers; + private final Iterable observers; - public JdbcObserverImpl(SharedSessionContractImplementor session) { + public JdbcObserverImpl(SharedSessionContractImplementor session, FastSessionServices fastSessionServices) { this.session = session; - this.observers = new ArrayList<>(); - this.observers.add( new ConnectionObserverStatsBridge( session.getFactory() ) ); + this.observers = fastSessionServices.getDefaultJdbcObservers(); } @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 da6c9f3ca753..954593d6d55a 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" ); From 269d5f835860b188df81622d369bc37e61a35ea5 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Thu, 22 Aug 2019 08:47:50 +0100 Subject: [PATCH 57/99] HHH-13565 Review allocations for default SessionEventListener instances --- .../BaselineSessionEventsListenerBuilder.java | 69 +++++++--- .../SessionEventListenerManagerImpl.java | 118 ++++++++++-------- .../AbstractSharedSessionContract.java | 12 +- .../internal/FastSessionServices.java | 9 +- .../NonContextualJdbcConnectionAccess.java | 3 + .../internal/SessionCreationOptions.java | 8 ++ .../internal/SessionFactoryImpl.java | 39 ++++-- .../SessionEventListenersManagerTest.java | 66 ++++++++++ 8 files changed, 242 insertions(+), 82 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/event/SessionEventListenersManagerTest.java 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 5a2b8a419006..487b201c25ce 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 @@ * @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 boolean isLogSessionMetrics() { } @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 getAutoListener() { } @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() ); + 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 ); } - if ( autoListener != null ) { - try { - list.add( autoListener.newInstance() ); - } - catch (Exception e) { - throw new HibernateException( - "Unable to instantiate specified auto SessionEventListener : " + autoListener.getName(), - e - ); - } + else if ( !addStats && !addAutoListener ) { + arr = EMPTY; } - return list; + 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/engine/internal/SessionEventListenerManagerImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/SessionEventListenerManagerImpl.java index a79dac965714..52261bfeab81 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 @@ * @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/internal/AbstractSharedSessionContract.java b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java index 5a90b841fdca..d89aeb710b9c 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java @@ -32,6 +32,7 @@ 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.ClassLoadingException; @@ -136,7 +137,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont protected boolean waitingForAutoClose; // transient & non-final for Serialization purposes - ugh - private transient SessionEventListenerManagerImpl sessionEventsManager = new SessionEventListenerManagerImpl(); + private transient SessionEventListenerManagerImpl sessionEventsManager; private transient EntityNameResolver entityNameResolver; private Integer jdbcBatchSize; @@ -168,6 +169,13 @@ public AbstractSharedSessionContract(SessionFactoryImpl factory, SessionCreation 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, fastSessionServices ); @@ -1190,7 +1198,6 @@ private void readObject(ObjectInputStream ois) throws IOException, ClassNotFound // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Step 1 :: read back non-transient state... ois.defaultReadObject(); - sessionEventsManager = new SessionEventListenerManagerImpl(); // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Step 2 :: read back transient state... @@ -1198,6 +1205,7 @@ private void readObject(ObjectInputStream ois) throws IOException, ClassNotFound factory = SessionFactoryImpl.deserialize( ois ); fastSessionServices = factory.getFastSessionServices(); + sessionEventsManager = new SessionEventListenerManagerImpl( fastSessionServices.defaultSessionEventListeners.buildBaseline() ); jdbcSessionContext = new JdbcSessionContextImpl( this, (StatementInspector) ois.readObject(), fastSessionServices ); jdbcCoordinator = JdbcCoordinatorImpl.deserialize( ois, this ); diff --git a/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java b/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java index c0e7a29d4811..afa3bbede111 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java @@ -10,6 +10,8 @@ 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; @@ -115,6 +117,7 @@ final class FastSessionServices { final boolean isJtaTransactionAccessible; final CacheMode initialSessionCacheMode; final boolean discardOnClose; + final BaselineSessionEventsListenerBuilder defaultSessionEventListeners; //Private fields: private final Dialect dialect; @@ -126,6 +129,7 @@ final class FastSessionServices { 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 ); @@ -150,7 +154,7 @@ final class FastSessionServices { //Other highly useful constants: this.dialect = jdbcServices.getJdbcEnvironment().getDialect(); - this.disallowOutOfTransactionUpdateOperations = !sf.getSessionFactoryOptions().isAllowOutOfTransactionUpdateOperations(); + this.disallowOutOfTransactionUpdateOperations = !sessionFactoryOptions.isAllowOutOfTransactionUpdateOperations(); this.useStreamForLobBinding = Environment.useStreamsForBinary() || dialect.useInputStreamToInsertBlob(); this.requiresMultiTenantConnectionProvider = sf.getSettings().getMultiTenancyStrategy().requiresMultiTenantConnectionProvider(); @@ -167,8 +171,9 @@ final class FastSessionServices { this.defaultCacheStoreMode = determineCacheStoreMode( defaultSessionProperties ); this.defaultCacheRetrieveMode = determineCacheRetrieveMode( defaultSessionProperties ); this.initialSessionCacheMode = CacheModeHelper.interpretCacheMode( defaultCacheStoreMode, defaultCacheRetrieveMode ); - this.discardOnClose = sf.getSessionFactoryOptions().isReleaseResourcesOnCloseEnabled(); + this.discardOnClose = sessionFactoryOptions.isReleaseResourcesOnCloseEnabled(); this.defaultJdbcObservers = Collections.singletonList( new ConnectionObserverStatsBridge( sf ) ); + this.defaultSessionEventListeners = sessionFactoryOptions.getBaselineSessionEventsListenerBuilder(); } Iterable getClearEventListeners() { 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 4cbe9573c97e..82df15f40fa8 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 @@ 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 a647708e1860..ed2920662e7c 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 af25ea4876ec..573863847f09 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java @@ -1156,6 +1156,9 @@ static class SessionBuilderImpl implements SessionBuil 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 @@ -1178,9 +1181,7 @@ static class SessionBuilderImpl implements SessionBuil tenantIdentifier = currentTenantIdentifierResolver.resolveCurrentTenantIdentifier(); } this.jdbcTimeZone = sessionFactoryOptions.getJdbcTimeZone(); - - listeners = sessionFactoryOptions.getBaselineSessionEventsListenerBuilder().buildBaselineList(); - queryParametersValidationEnabled = sessionFactoryOptions.isQueryParametersValidationEnabled(); + this.queryParametersValidationEnabled = sessionFactoryOptions.isQueryParametersValidationEnabled(); } @@ -1268,20 +1269,18 @@ public TimeZone getJdbcTimeZone() { 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 @@ -1377,6 +1376,11 @@ public T tenantIdentifier(String tenantIdentifier) { @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; } @@ -1384,7 +1388,13 @@ public T eventListeners(SessionEventListener... listeners) { @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; } @@ -1484,6 +1494,11 @@ public TimeZone getJdbcTimeZone() { return sessionFactory.getSessionFactoryOptions().getJdbcTimeZone(); } + @Override + public List getCustomSessionEventListener() { + return null; + } + @Override public SessionOwner getSessionOwner() { return null; 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 000000000000..d4eae4acda37 --- /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 ); + } + + } + +} From b8f3fc36164a5bb70349039ba6325d560a77a041 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Mon, 19 Aug 2019 22:31:24 +0100 Subject: [PATCH 58/99] HHH-13565 Convert iteration of event listeners to be allocation free --- .../internal/EventListenerGroupImpl.java | 27 +++- .../event/service/spi/EventListenerGroup.java | 24 +++ .../internal/FastSessionServices.java | 150 +++++------------- .../org/hibernate/internal/SessionImpl.java | 136 +++++----------- 4 files changed, 132 insertions(+), 205 deletions(-) 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 8d5200c75139..20e9d5c40ab3 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 @@ -9,9 +9,10 @@ import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashSet; -import java.util.List; import java.util.ListIterator; 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.EventListenerGroup; @@ -28,7 +29,10 @@ class EventListenerGroupImpl implements EventListenerGroup { private final EventListenerRegistryImpl listenerRegistry; private final Set duplicationStrategies = new LinkedHashSet<>(); - private List listeners; + + // Performance: make sure a forEach iteration on this type is efficient; in particular we choose ArrayList + // so to avoid allocating iterators. + private ArrayList listeners; public EventListenerGroupImpl(EventType eventType, EventListenerRegistryImpl listenerRegistry) { this.eventType = eventType; @@ -75,6 +79,25 @@ public void clear() { } } + @Override + public final void fireLazyEventOnEachListener(final Supplier eventSupplier, final BiConsumer actionOnEvent) { + if ( listeners != null && listeners.size() != 0 ) { + final U event = eventSupplier.get(); + for ( T listener : this.listeners ) { + actionOnEvent.accept( listener, event ); + } + } + } + + @Override + public final void fireEventOnEachListener(final U event, final BiConsumer actionOnEvent) { + if ( listeners != null ) { + for ( T listener : this.listeners ) { + actionOnEvent.accept( listener, event ); + } + } + } + @Override public void addDuplicationStrategy(DuplicationStrategy strategy) { duplicationStrategies.add( strategy ); 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 fad69d143dc7..1f4e823a84df 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,6 +7,8 @@ package org.hibernate.event.service.spi; import java.io.Serializable; +import java.util.function.BiConsumer; +import java.util.function.Supplier; import org.hibernate.event.spi.EventType; @@ -54,4 +56,26 @@ 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 + */ + 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 + */ + void fireEventOnEachListener(final U event, final BiConsumer actionOnEvent); + } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java b/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java index afa3bbede111..f3367e27015e 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java @@ -18,6 +18,7 @@ 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; @@ -85,25 +86,24 @@ final class FastSessionServices { final Map defaultSessionProperties; // All session events need to be iterated frequently: - - private final Iterable clearEventListeners; - private final Iterable saveUpdateEventListeners; - private final Iterable evictEventListeners; - private final Iterable saveEventListeners; - private final Iterable updateEventListeners; - private final Iterable lockEventListeners; - private final Iterable persistEventListeners; - private final Iterable persistOnFlushEventListeners; - private final Iterable mergeEventListeners; - private final Iterable deleteEventListeners; - private final Iterable loadEventListeners; - private final Iterable refreshEventListeners; - private final Iterable replicateEventListeners; - private final Iterable autoFlushEventListeners; - private final Iterable dirtyCheckEventListeners; - private final Iterable flushEventListeners; - private final Iterable initCollectionEventListeners; - private final Iterable resolveNaturalIdEventListeners; + 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; @@ -133,24 +133,24 @@ final class FastSessionServices { // Pre-compute all iterators on Event listeners: final EventListenerRegistry eventListenerRegistry = sr.getService( EventListenerRegistry.class ); - this.clearEventListeners = listeners( eventListenerRegistry, EventType.CLEAR ); - this.saveUpdateEventListeners = listeners( eventListenerRegistry, EventType.SAVE_UPDATE ); - this.evictEventListeners = listeners( eventListenerRegistry, EventType.EVICT ); - this.saveEventListeners = listeners( eventListenerRegistry, EventType.SAVE ); - this.updateEventListeners = listeners( eventListenerRegistry, EventType.UPDATE ); - this.lockEventListeners = listeners( eventListenerRegistry, EventType.LOCK ); - this.persistEventListeners = listeners( eventListenerRegistry, EventType.PERSIST ); - this.persistOnFlushEventListeners = listeners( eventListenerRegistry, EventType.PERSIST_ONFLUSH ); - this.mergeEventListeners = listeners( eventListenerRegistry, EventType.MERGE ); - this.deleteEventListeners = listeners( eventListenerRegistry, EventType.DELETE ); - this.loadEventListeners = listeners( eventListenerRegistry, EventType.LOAD ); - this.refreshEventListeners = listeners( eventListenerRegistry, EventType.REFRESH ); - this.replicateEventListeners = listeners( eventListenerRegistry, EventType.REPLICATE ); - this.autoFlushEventListeners = listeners( eventListenerRegistry, EventType.AUTO_FLUSH ); - this.dirtyCheckEventListeners = listeners( eventListenerRegistry, EventType.DIRTY_CHECK ); - this.flushEventListeners = listeners( eventListenerRegistry, EventType.FLUSH ); - this.initCollectionEventListeners = listeners( eventListenerRegistry, EventType.INIT_COLLECTION ); - this.resolveNaturalIdEventListeners = listeners( eventListenerRegistry, EventType.RESOLVE_NATURAL_ID ); + 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(); @@ -176,80 +176,8 @@ final class FastSessionServices { this.defaultSessionEventListeners = sessionFactoryOptions.getBaselineSessionEventsListenerBuilder(); } - Iterable getClearEventListeners() { - return clearEventListeners; - } - - Iterable getEvictEventListeners() { - return this.evictEventListeners; - } - - Iterable getDirtyCheckEventListeners() { - return this.dirtyCheckEventListeners; - } - - Iterable getSaveUpdateEventListeners() { - return this.saveUpdateEventListeners; - } - - Iterable getSaveEventListeners() { - return this.saveEventListeners; - } - - Iterable getUpdateEventListeners() { - return this.updateEventListeners; - } - - Iterable getLockEventListeners() { - return this.lockEventListeners; - } - - Iterable getPersistEventListeners() { - return persistEventListeners; - } - - Iterable getFlushEventListeners() { - return flushEventListeners; - } - - Iterable getPersistOnFlushEventListeners() { - return persistOnFlushEventListeners; - } - - Iterable getInitCollectionEventListeners() { - return initCollectionEventListeners; - } - - Iterable getLoadEventListeners() { - return loadEventListeners; - } - - Iterable getMergeEventListeners() { - return mergeEventListeners; - } - - Iterable getRefreshEventListeners() { - return refreshEventListeners; - } - - Iterable getDeleteEventListeners() { - return deleteEventListeners; - } - - Iterable getAutoFlushEventListeners() { - return autoFlushEventListeners; - } - - Iterable getReplicateEventListeners() { - return replicateEventListeners; - } - - Iterable getResolveNaturalIdEventListeners() { - return resolveNaturalIdEventListeners; - } - - private static Iterable listeners(EventListenerRegistry elr, EventType type) { - return elr.getEventListenerGroup( type ).listeners(); + private static EventListenerGroup listeners(EventListenerRegistry elr, EventType type) { + return elr.getEventListenerGroup( type ); } SqlTypeDescriptor remapSqlTypeDescriptor(SqlTypeDescriptor sqlTypeDescriptor) { 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 004149dac0f9..7b1a04c84792 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -307,13 +307,12 @@ private void internalClear() { persistenceContext.clear(); actionQueue.clear(); - final ClearEvent event = new ClearEvent( this ); - for ( ClearEventListener listener : fastSessionServices.getClearEventListeners() ) { - listener.onClear( event ); - } + fastSessionServices.eventListenerGroup_CLEAR.fireLazyEventOnEachListener( this::createClearEvent, ClearEventListener::onClear ); } - + private ClearEvent createClearEvent() { + return new ClearEvent( this ); + } @Override @SuppressWarnings("StatementWithEmptyBody") @@ -578,13 +577,11 @@ public void saveOrUpdate(String entityName, Object obj) throws HibernateExceptio fireSaveOrUpdate( new SaveOrUpdateEvent( entityName, obj, this ) ); } - private void fireSaveOrUpdate(SaveOrUpdateEvent event) { + private void fireSaveOrUpdate(final SaveOrUpdateEvent event) { checkOpen(); checkTransactionSynchStatus(); checkNoUnresolvedActionsBeforeOperation(); - for ( SaveOrUpdateEventListener listener : fastSessionServices.getSaveUpdateEventListeners() ) { - listener.onSaveOrUpdate( event ); - } + fastSessionServices.eventListenerGroup_SAVE_UPDATE.fireEventOnEachListener( event, SaveOrUpdateEventListener::onSaveOrUpdate ); checkNoUnresolvedActionsAfterOperation(); } @@ -600,13 +597,11 @@ public Serializable save(String entityName, Object object) throws HibernateExcep return fireSave( new SaveOrUpdateEvent( entityName, object, this ) ); } - private Serializable fireSave(SaveOrUpdateEvent event) { + private Serializable fireSave(final SaveOrUpdateEvent event) { checkOpen(); checkTransactionSynchStatus(); checkNoUnresolvedActionsBeforeOperation(); - for ( SaveOrUpdateEventListener listener : fastSessionServices.getSaveEventListeners() ) { - listener.onSaveOrUpdate( event ); - } + fastSessionServices.eventListenerGroup_SAVE.fireEventOnEachListener( event, SaveOrUpdateEventListener::onSaveOrUpdate ); checkNoUnresolvedActionsAfterOperation(); return event.getResultId(); } @@ -628,9 +623,7 @@ private void fireUpdate(SaveOrUpdateEvent event) { checkOpen(); checkTransactionSynchStatus(); checkNoUnresolvedActionsBeforeOperation(); - for ( SaveOrUpdateEventListener listener : fastSessionServices.getUpdateEventListeners() ) { - listener.onSaveOrUpdate( event ); - } + fastSessionServices.eventListenerGroup_UPDATE.fireEventOnEachListener( event, SaveOrUpdateEventListener::onSaveOrUpdate ); checkNoUnresolvedActionsAfterOperation(); } @@ -663,13 +656,10 @@ private void fireLock(Object object, LockOptions options) { private void fireLock(LockEvent event) { checkOpen(); pulseTransactionCoordinator(); - for ( LockEventListener listener : fastSessionServices.getLockEventListeners() ) { - listener.onLock( event ); - } + fastSessionServices.eventListenerGroup_LOCK.fireEventOnEachListener( event, LockEventListener::onLock ); delayedAfterCompletion(); } - // persist() operations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @Override @@ -690,14 +680,12 @@ public void persist(String entityName, Object object, Map copiedAlready) throws firePersist( copiedAlready, new PersistEvent( entityName, object, this ) ); } - private void firePersist(PersistEvent event) { + private void firePersist(final PersistEvent event) { try { checkTransactionSynchStatus(); checkNoUnresolvedActionsBeforeOperation(); - for ( PersistEventListener listener : fastSessionServices.getPersistEventListeners() ) { - listener.onPersist( event ); - } + fastSessionServices.eventListenerGroup_PERSIST.fireEventOnEachListener( event, PersistEventListener::onPersist ); } catch (MappingException e) { throw getExceptionConverter().convert( new IllegalArgumentException( e.getMessage() ) ); @@ -715,13 +703,13 @@ private void firePersist(PersistEvent event) { } } - private void firePersist(Map copiedAlready, PersistEvent event) { + private void firePersist(final Map copiedAlready, final PersistEvent event) { pulseTransactionCoordinator(); try { - for ( PersistEventListener listener : fastSessionServices.getPersistEventListeners() ) { - 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, (l, e) -> l.onPersist( e, copiedAlready ) ); } catch ( MappingException e ) { throw getExceptionConverter().convert( new IllegalArgumentException( e.getMessage() ) ) ; @@ -752,22 +740,18 @@ public void persistOnFlush(String entityName, Object object, Map copiedAlready) firePersistOnFlush( copiedAlready, new PersistEvent( entityName, object, this ) ); } - private void firePersistOnFlush(Map copiedAlready, PersistEvent event) { + private void firePersistOnFlush(final Map copiedAlready, final PersistEvent event) { checkOpenOrWaitingForAutoClose(); pulseTransactionCoordinator(); - for ( PersistEventListener listener : fastSessionServices.getPersistOnFlushEventListeners() ) { - listener.onPersist( event, copiedAlready ); - } + fastSessionServices.eventListenerGroup_PERSIST_ONFLUSH.fireEventOnEachListener( event, (l,e) -> l.onPersist( e, copiedAlready ) ); delayedAfterCompletion(); } - private void firePersistOnFlush(PersistEvent event) { + private void firePersistOnFlush(final PersistEvent event) { checkOpen(); checkTransactionSynchStatus(); checkNoUnresolvedActionsBeforeOperation(); - for ( PersistEventListener listener : fastSessionServices.getPersistOnFlushEventListeners() ) { - listener.onPersist( event ); - } + fastSessionServices.eventListenerGroup_PERSIST_ONFLUSH.fireEventOnEachListener( event, PersistEventListener::onPersist ); checkNoUnresolvedActionsAfterOperation(); } @@ -796,9 +780,7 @@ private Object fireMerge(MergeEvent event) { try { checkTransactionSynchStatus(); checkNoUnresolvedActionsBeforeOperation(); - for ( MergeEventListener listener : fastSessionServices.getMergeEventListeners() ) { - listener.onMerge( event ); - } + fastSessionServices.eventListenerGroup_MERGE.fireEventOnEachListener( event, MergeEventListener::onMerge ); checkNoUnresolvedActionsAfterOperation(); } catch ( ObjectDeletedException sse ) { @@ -815,12 +797,10 @@ private Object fireMerge(MergeEvent event) { return event.getResult(); } - private void fireMerge(Map copiedAlready, MergeEvent event) { + private void fireMerge(final Map copiedAlready, final MergeEvent event) { try { pulseTransactionCoordinator(); - for ( MergeEventListener listener : fastSessionServices.getMergeEventListeners() ) { - listener.onMerge( event, copiedAlready ); - } + fastSessionServices.eventListenerGroup_MERGE.fireEventOnEachListener( event, (l,e) -> l.onMerge( e, copiedAlready ) ); } catch ( ObjectDeletedException sse ) { throw getExceptionConverter().convert( new IllegalArgumentException( sse ) ); @@ -905,12 +885,10 @@ private void logRemoveOrphanBeforeUpdates(String timing, String entityName, Obje } } - private void fireDelete(DeleteEvent event) { + private void fireDelete(final DeleteEvent event) { try{ pulseTransactionCoordinator(); - for ( DeleteEventListener listener : fastSessionServices.getDeleteEventListeners() ) { - listener.onDelete( event ); - } + fastSessionServices.eventListenerGroup_DELETE.fireEventOnEachListener( event, DeleteEventListener::onDelete ); } catch ( ObjectDeletedException sse ) { throw getExceptionConverter().convert( new IllegalArgumentException( sse ) ); @@ -927,12 +905,10 @@ private void fireDelete(DeleteEvent event) { } } - private void fireDelete(DeleteEvent event, Set transientEntities) { + private void fireDelete(final DeleteEvent event, final Set transientEntities) { try{ pulseTransactionCoordinator(); - for ( DeleteEventListener listener : fastSessionServices.getDeleteEventListeners() ) { - listener.onDelete( event, transientEntities ); - } + fastSessionServices.eventListenerGroup_DELETE.fireEventOnEachListener( event, (l,e) -> l.onDelete( e, transientEntities ) ); } catch ( ObjectDeletedException sse ) { throw getExceptionConverter().convert( new IllegalArgumentException( sse ) ); @@ -1205,19 +1181,15 @@ private void fireLoad(LoadEvent event, LoadType loadType) { // 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 : fastSessionServices.getLoadEventListeners() ) { - listener.onLoad( event, loadType ); - } + fastSessionServices.eventListenerGroup_LOAD.fireEventOnEachListener( event, (l,e) -> l.onLoad( e, loadType ) ); } - private void fireResolveNaturalId(ResolveNaturalIdEvent event) { + private void fireResolveNaturalId(final ResolveNaturalIdEvent event) { checkOpenOrWaitingForAutoClose(); pulseTransactionCoordinator(); - for ( ResolveNaturalIdEventListener listener : fastSessionServices.getResolveNaturalIdEventListeners() ) { - listener.onResolveNaturalId( event ); - } + fastSessionServices.eventListenerGroup_RESOLVE_NATURAL_ID.fireEventOnEachListener( event, ResolveNaturalIdEventListener::onResolveNaturalId ); delayedAfterCompletion(); } @@ -1260,7 +1232,7 @@ public void refresh(String entityName, Object object, Map refreshedAlready) thro 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 ) { @@ -1275,9 +1247,7 @@ private void fireRefresh(RefreshEvent event) { } } pulseTransactionCoordinator(); - for ( RefreshEventListener listener : fastSessionServices.getRefreshEventListeners() ) { - listener.onRefresh( event ); - } + fastSessionServices.eventListenerGroup_REFRESH.fireEventOnEachListener( event, RefreshEventListener::onRefresh ); } catch (RuntimeException e) { if ( !getSessionFactory().getSessionFactoryOptions().isJpaBootstrap() ) { @@ -1293,12 +1263,10 @@ private void fireRefresh(RefreshEvent event) { } } - private void fireRefresh(Map refreshedAlready, RefreshEvent event) { + private void fireRefresh(final Map refreshedAlready, final RefreshEvent event) { try { pulseTransactionCoordinator(); - for ( RefreshEventListener listener : fastSessionServices.getRefreshEventListeners() ) { - listener.onRefresh( event, refreshedAlready ); - } + fastSessionServices.eventListenerGroup_REFRESH.fireEventOnEachListener( event, (l,e) -> l.onRefresh( e, refreshedAlready ) ); } catch (RuntimeException e) { throw getExceptionConverter().convert( e ); @@ -1322,12 +1290,10 @@ public void replicate(String entityName, Object obj, ReplicationMode replication fireReplicate( new ReplicateEvent( entityName, obj, replicationMode, this ) ); } - private void fireReplicate(ReplicateEvent event) { + private void fireReplicate(final ReplicateEvent event) { checkOpen(); pulseTransactionCoordinator(); - for ( ReplicateEventListener listener : fastSessionServices.getReplicateEventListeners() ) { - listener.onReplicate( event ); - } + fastSessionServices.eventListenerGroup_REPLICATE.fireEventOnEachListener( event, ReplicateEventListener::onReplicate ); delayedAfterCompletion(); } @@ -1340,15 +1306,10 @@ private void fireReplicate(ReplicateEvent event) { */ @Override public void evict(Object object) throws HibernateException { - fireEvict( new EvictEvent( object, this ) ); - } - - private void fireEvict(EvictEvent event) { checkOpen(); pulseTransactionCoordinator(); - for ( EvictEventListener listener : fastSessionServices.getEvictEventListeners() ) { - listener.onEvict( event ); - } + final EvictEvent event = new EvictEvent( object, this ); + fastSessionServices.eventListenerGroup_EVICT.fireEventOnEachListener( event, EvictEventListener::onEvict ); delayedAfterCompletion(); } @@ -1363,9 +1324,7 @@ protected boolean autoFlushIfRequired(Set querySpaces) throws HibernateException return false; } AutoFlushEvent event = new AutoFlushEvent( querySpaces, this ); - for ( AutoFlushEventListener listener : fastSessionServices.getAutoFlushEventListeners() ) { - listener.onAutoFlush( event ); - } + fastSessionServices.eventListenerGroup_AUTO_FLUSH.fireEventOnEachListener( event, AutoFlushEventListener::onAutoFlush ); return event.isFlushRequired(); } @@ -1379,9 +1338,7 @@ public boolean isDirty() throws HibernateException { return true; } DirtyCheckEvent event = new DirtyCheckEvent( this ); - for ( DirtyCheckEventListener listener : fastSessionServices.getDirtyCheckEventListeners() ) { - listener.onDirtyCheck( event ); - } + fastSessionServices.eventListenerGroup_DIRTY_CHECK.fireEventOnEachListener( event, DirtyCheckEventListener::onDirtyCheck ); delayedAfterCompletion(); return event.isDirty(); } @@ -1401,11 +1358,8 @@ private void doFlush() { throw new HibernateException( "Flush during cascade is dangerous" ); } - FlushEvent flushEvent = new FlushEvent( this ); - for ( FlushEventListener listener : fastSessionServices.getFlushEventListeners() ) { - listener.onFlush( flushEvent ); - } - + FlushEvent event = new FlushEvent( this ); + fastSessionServices.eventListenerGroup_FLUSH.fireEventOnEachListener( event, FlushEventListener::onFlush ); delayedAfterCompletion(); } catch ( RuntimeException e ) { @@ -2206,9 +2160,7 @@ public void initializeCollection(PersistentCollection collection, boolean writin checkOpenOrWaitingForAutoClose(); pulseTransactionCoordinator(); InitializeCollectionEvent event = new InitializeCollectionEvent( collection, this ); - for ( InitializeCollectionEventListener listener : fastSessionServices.getInitCollectionEventListeners() ) { - listener.onInitializeCollection( event ); - } + fastSessionServices.eventListenerGroup_INIT_COLLECTION.fireEventOnEachListener( event, InitializeCollectionEventListener::onInitializeCollection ); delayedAfterCompletion(); } From 91299aeb7faa703c67e72f4e9d4b7c3a1e61106e Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Tue, 20 Aug 2019 21:10:07 +0100 Subject: [PATCH 59/99] HHH-13565 Remove some dead code from SessionImpl --- .../org/hibernate/internal/SessionImpl.java | 26 ++----------------- 1 file changed, 2 insertions(+), 24 deletions(-) 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 7b1a04c84792..6743b4426c3c 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -725,37 +725,15 @@ private void firePersist(final Map copiedAlready, final PersistEvent event) { // 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(final Map copiedAlready, final PersistEvent event) { + public void persistOnFlush(String entityName, Object object, Map copiedAlready) { checkOpenOrWaitingForAutoClose(); pulseTransactionCoordinator(); + PersistEvent event = new PersistEvent( entityName, object, this ); fastSessionServices.eventListenerGroup_PERSIST_ONFLUSH.fireEventOnEachListener( event, (l,e) -> l.onPersist( e, copiedAlready ) ); delayedAfterCompletion(); } - private void firePersistOnFlush(final PersistEvent event) { - checkOpen(); - checkTransactionSynchStatus(); - checkNoUnresolvedActionsBeforeOperation(); - fastSessionServices.eventListenerGroup_PERSIST_ONFLUSH.fireEventOnEachListener( event, PersistEventListener::onPersist ); - checkNoUnresolvedActionsAfterOperation(); - } - - // merge() operations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @Override From cc39f54717f9c6761a108dc7c14ab77533cf8766 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Tue, 20 Aug 2019 21:18:38 +0100 Subject: [PATCH 60/99] HHH-13565 Micro cleanup of Trace level checks --- .../org/hibernate/internal/SessionImpl.java | 13 +++++++---- .../internal/ResultSetProcessorImpl.java | 23 ++++++++++++++----- 2 files changed, 25 insertions(+), 11 deletions(-) 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 6743b4426c3c..19130390b8da 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -814,7 +814,9 @@ public void delete(String entityName, Object object) throws HibernateException { 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( @@ -822,12 +824,12 @@ public void delete(String entityName, Object object, boolean isCascadeDeleteEnab entityName, object, isCascadeDeleteEnabled, - persistenceContext.isRemovingOrphanBeforeUpates(), + removingOrphanBeforeUpates, this ), transientEntities ); - if ( log.isTraceEnabled() && persistenceContext.isRemovingOrphanBeforeUpates() ) { + if ( traceEnabled && removingOrphanBeforeUpates ) { logRemoveOrphanBeforeUpdates( "after continuing", entityName, object ); } } @@ -836,7 +838,8 @@ public void delete(String entityName, Object object, boolean isCascadeDeleteEnab 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(); @@ -846,7 +849,7 @@ public void removeOrphanBeforeUpdates(String entityName, Object child) { } finally { persistenceContext.endRemoveOrphanBeforeUpdates(); - if ( log.isTraceEnabled() ) { + if ( traceEnabled ) { logRemoveOrphanBeforeUpdates( "end", entityName, child ); } } 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 c710b2e01ccf..81c32a0faed9 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 @@ -82,13 +82,20 @@ public List extractResults( 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; } @@ -109,12 +116,14 @@ public List extractResults( 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 ); @@ -125,7 +134,9 @@ public List extractResults( 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(); From 2de048fde5872d0149798513448802d77fea92c3 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Tue, 20 Aug 2019 21:59:16 +0100 Subject: [PATCH 61/99] HHH-13565 Prepare the most commonly needed SessionBuilder instances in advance --- .../hibernate/internal/SessionFactoryImpl.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) 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 573863847f09..c0176cffe10b 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java @@ -198,6 +198,8 @@ 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, @@ -377,6 +379,11 @@ public void sessionFactoryClosed(SessionFactory factory) { 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 ); @@ -474,15 +481,11 @@ private JdbcConnectionAccess buildLocalConnectionAccess() { } 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 { From 646a8756a9cfa41425826d7cd6f9608a0b6c9e54 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Wed, 21 Aug 2019 09:16:38 +0100 Subject: [PATCH 62/99] HHH-13565 Making SessionFactoryImpl#LockOptions lazily initialized as well --- .../internal/FastSessionServices.java | 9 +++ .../org/hibernate/internal/SessionImpl.java | 75 ++++++------------ .../jpa/internal/util/LockOptionsHelper.java | 77 +++++++++++++++++++ 3 files changed, 111 insertions(+), 50 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/jpa/internal/util/LockOptionsHelper.java diff --git a/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java b/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java index f3367e27015e..e1688c329307 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java @@ -39,6 +39,7 @@ 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; @@ -118,6 +119,7 @@ final class FastSessionServices { final CacheMode initialSessionCacheMode; final boolean discardOnClose; final BaselineSessionEventsListenerBuilder defaultSessionEventListeners; + final LockOptions defaultLockOptions; //Private fields: private final Dialect dialect; @@ -174,6 +176,13 @@ final class FastSessionServices { this.discardOnClose = sessionFactoryOptions.isReleaseResourcesOnCloseEnabled(); this.defaultJdbcObservers = Collections.singletonList( 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) { 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 19130390b8da..c393a8ce532b 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -145,6 +145,7 @@ 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; @@ -207,8 +208,7 @@ public final class SessionImpl 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; @@ -248,7 +248,10 @@ public SessionImpl(SessionFactoryImpl factory, SessionCreationOptions options) { statistics.openSession(); } - setLockOptions( this.properties == null ? fastSessionServices.defaultSessionProperties : this.properties, this.lockOptions ); + 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 @@ -259,9 +262,21 @@ public SessionImpl(SessionFactoryImpl factory, SessionCreationOptions options) { } } + private LockOptions getLockOptionsForRead() { + return this.lockOptions == null ? fastSessionServices.defaultLockOptions : this.lockOptions; + } + + 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 = getSessionProperty( QueryHints.SPEC_HINT_TIMEOUT ) ) != null ) { @@ -3255,57 +3270,17 @@ public SessionImplementor getSession() { @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 ); - } - } - } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -3572,7 +3547,7 @@ public void setProperty(String propertyName, Object value) { setHibernateFlushMode( ConfigurationHelper.getFlushMode( value, FlushMode.AUTO ) ); } else if ( JPA_LOCK_SCOPE.equals( propertyName ) || JPA_LOCK_TIMEOUT.equals( propertyName ) ) { - setLockOptions( properties, this.lockOptions ); + LockOptionsHelper.applyPropertiesToLockOptions( properties, this::getLockOptionsForWrite ); } else if ( JPA_SHARED_CACHE_RETRIEVE_MODE.equals( propertyName ) || JPA_SHARED_CACHE_STORE_MODE.equals( propertyName ) ) { getSession().setCacheMode( 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 000000000000..20e1ea641b79 --- /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 ); + } + } + } + +} From 9bfffd85d7beeaee98630bb73dbbb3fb7a11f5e7 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Wed, 21 Aug 2019 11:48:18 +0100 Subject: [PATCH 63/99] HHH-13565 Ensure all events from EventListenerGroup can be fired without allocations --- .../internal/EventListenerGroupImpl.java | 95 +++++++++++++++---- .../service/spi/EventActionWithParameter.java | 17 ++++ .../event/service/spi/EventListenerGroup.java | 13 +++ .../org/hibernate/internal/SessionImpl.java | 12 +-- 4 files changed, 112 insertions(+), 25 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/event/service/spi/EventActionWithParameter.java 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 20e9d5c40ab3..3f4cc95fd713 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,15 +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; @@ -23,6 +27,7 @@ /** * @author Steve Ebersole + * @author Sanne Grinovero */ class EventListenerGroupImpl implements EventListenerGroup { private EventType eventType; @@ -30,9 +35,14 @@ class EventListenerGroupImpl implements EventListenerGroup { private final Set duplicationStrategies = new LinkedHashSet<>(); - // Performance: make sure a forEach iteration on this type is efficient; in particular we choose ArrayList - // so to avoid allocating iterators. - private ArrayList 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; @@ -66,7 +76,8 @@ public boolean isEmpty() { @Override public int count() { - return listeners == null ? 0 : listeners.size(); + final T[] ls = listeners; + return ls == null ? 0 : ls.length; } @Override @@ -74,16 +85,16 @@ public void clear() { 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) { - if ( listeners != null && listeners.size() != 0 ) { + final T[] ls = listeners; + if ( ls != null && ls.length != 0 ) { final U event = eventSupplier.get(); - for ( T listener : this.listeners ) { + for ( T listener : ls ) { actionOnEvent.accept( listener, event ); } } @@ -91,13 +102,24 @@ public final void fireLazyEventOnEachListener(final Supplier eventSupplie @Override public final void fireEventOnEachListener(final U event, final BiConsumer actionOnEvent) { - if ( listeners != null ) { - for ( T listener : this.listeners ) { + 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 ); + } + } + } + @Override public void addDuplicationStrategy(DuplicationStrategy strategy) { duplicationStrategies.add( strategy ); @@ -105,22 +127,44 @@ public void addDuplicationStrategy(DuplicationStrategy strategy) { /** * 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 ); } @@ -129,28 +173,39 @@ public void appendListener(T listener) { @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 ) ) { @@ -180,7 +235,8 @@ private boolean listenerShouldGetAdded(T listener) { 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) { @@ -206,6 +262,7 @@ private void checkAgainstBaseInterface(T listener) { 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 000000000000..e685b5293bee --- /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 1f4e823a84df..213f6acb64be 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,9 +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; /** @@ -35,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(); /** @@ -67,6 +75,7 @@ public interface EventListenerGroup extends Serializable { * @param actionOnEvent * @param the kind of event */ + @Incubating void fireLazyEventOnEachListener(final Supplier eventSupplier, final BiConsumer actionOnEvent); /** @@ -76,6 +85,10 @@ public interface EventListenerGroup extends Serializable { * @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/internal/SessionImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java index c393a8ce532b..656feaa482e3 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -724,7 +724,7 @@ private void firePersist(final Map copiedAlready, final PersistEvent event) { try { //Uses a capturing lambda in this case as we need to carry the additional Map parameter: fastSessionServices.eventListenerGroup_PERSIST - .fireEventOnEachListener( event, (l, e) -> l.onPersist( e, copiedAlready ) ); + .fireEventOnEachListener( event, copiedAlready, PersistEventListener::onPersist ); } catch ( MappingException e ) { throw getExceptionConverter().convert( new IllegalArgumentException( e.getMessage() ) ) ; @@ -745,7 +745,7 @@ public void persistOnFlush(String entityName, Object object, Map copiedAlready) checkOpenOrWaitingForAutoClose(); pulseTransactionCoordinator(); PersistEvent event = new PersistEvent( entityName, object, this ); - fastSessionServices.eventListenerGroup_PERSIST_ONFLUSH.fireEventOnEachListener( event, (l,e) -> l.onPersist( e, copiedAlready ) ); + fastSessionServices.eventListenerGroup_PERSIST_ONFLUSH.fireEventOnEachListener( event, copiedAlready, PersistEventListener::onPersist ); delayedAfterCompletion(); } @@ -793,7 +793,7 @@ private Object fireMerge(MergeEvent event) { private void fireMerge(final Map copiedAlready, final MergeEvent event) { try { pulseTransactionCoordinator(); - fastSessionServices.eventListenerGroup_MERGE.fireEventOnEachListener( event, (l,e) -> l.onMerge( e, copiedAlready ) ); + fastSessionServices.eventListenerGroup_MERGE.fireEventOnEachListener( event, copiedAlready, MergeEventListener::onMerge ); } catch ( ObjectDeletedException sse ) { throw getExceptionConverter().convert( new IllegalArgumentException( sse ) ); @@ -904,7 +904,7 @@ private void fireDelete(final DeleteEvent event) { private void fireDelete(final DeleteEvent event, final Set transientEntities) { try{ pulseTransactionCoordinator(); - fastSessionServices.eventListenerGroup_DELETE.fireEventOnEachListener( event, (l,e) -> l.onDelete( e, transientEntities ) ); + fastSessionServices.eventListenerGroup_DELETE.fireEventOnEachListener( event, transientEntities, DeleteEventListener::onDelete ); } catch ( ObjectDeletedException sse ) { throw getExceptionConverter().convert( new IllegalArgumentException( sse ) ); @@ -1179,7 +1179,7 @@ private void fireLoad(LoadEvent event, LoadType loadType) { // it seems they prevent these hot methods from being inlined. private void fireLoadNoChecks(final LoadEvent event, final LoadType loadType) { pulseTransactionCoordinator(); - fastSessionServices.eventListenerGroup_LOAD.fireEventOnEachListener( event, (l,e) -> l.onLoad( e, loadType ) ); + fastSessionServices.eventListenerGroup_LOAD.fireEventOnEachListener( event, loadType, LoadEventListener::onLoad ); } private void fireResolveNaturalId(final ResolveNaturalIdEvent event) { @@ -1262,7 +1262,7 @@ private void fireRefresh(final RefreshEvent event) { private void fireRefresh(final Map refreshedAlready, final RefreshEvent event) { try { pulseTransactionCoordinator(); - fastSessionServices.eventListenerGroup_REFRESH.fireEventOnEachListener( event, (l,e) -> l.onRefresh( e, refreshedAlready ) ); + fastSessionServices.eventListenerGroup_REFRESH.fireEventOnEachListener( event, refreshedAlready, RefreshEventListener::onRefresh ); } catch (RuntimeException e) { throw getExceptionConverter().convert( e ); From 618a502dacafb4d6d384a691d1c0e58d561d8094 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Tue, 20 Aug 2019 11:47:01 +0200 Subject: [PATCH 64/99] HHH-13592 Test AutoFlushEvent#isFlushRequired --- .../events/AutoFlushEventListenerTest.java | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/events/AutoFlushEventListenerTest.java 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 000000000000..4999ffb2b801 --- /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 ); + } + } +} From 5fc6012449e80da5fe253a968bc2b5eb65b519c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Tue, 20 Aug 2019 11:47:19 +0200 Subject: [PATCH 65/99] HHH-13592 Populate AutoFlushEvent#isFlushRequired with the correct value --- .../event/internal/DefaultAutoFlushEventListener.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 3683bbfd9f8b..ec90f240fb81 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 void onAutoFlush(AutoFlushEvent event) throws HibernateException { 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 void onAutoFlush(AutoFlushEvent event) throws HibernateException { } else { LOG.trace( "Don't need to execute flush" ); + event.setFlushRequired( false ); actionQueue.clearFromFlushNeededCheck( oldSize ); } - - event.setFlushRequired( flushIsReallyNeeded( event, source ) ); } } finally { From 13afce9afbbad93acecfde023743a2024ae13824 Mon Sep 17 00:00:00 2001 From: Elena Felder <41136058+elefeint@users.noreply.github.com> Date: Fri, 24 May 2019 17:11:10 -0400 Subject: [PATCH 66/99] HHH-13412 Move custom driver properties into their own section --- .../userguide/chapters/jdbc/Database_Access.adoc | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) 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 049e8e280f5a..e5eb26478b0c 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,13 @@ 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 +Any settings prefixed with `hibernate.connection.` (other than the "special ones"):: All such setting names 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 + + [[database-connectionprovider-c3p0]] === Using c3p0 @@ -48,13 +55,11 @@ 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] From ba1f15553c424f9f71dd35cf85689b757f59471b Mon Sep 17 00:00:00 2001 From: Elena Felder <41136058+elefeint@users.noreply.github.com> Date: Fri, 24 May 2019 17:29:09 -0400 Subject: [PATCH 67/99] HHH-13412 Remove stray newlines --- .../main/asciidoc/userguide/chapters/jdbc/Database_Access.adoc | 2 -- 1 file changed, 2 deletions(-) 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 e5eb26478b0c..5be8b9baaef0 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/jdbc/Database_Access.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/jdbc/Database_Access.adoc @@ -45,7 +45,6 @@ Otherwise, the no-arg form is used. `hibernate.connection.url`:: The JDBC connection url Any settings prefixed with `hibernate.connection.` (other than the "special ones"):: All such setting names 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 - [[database-connectionprovider-c3p0]] === Using c3p0 @@ -59,7 +58,6 @@ When c3p0 support is enabled, a number of c3p0-specific configuration settings a Transaction isolation of the Connections is managed by the `ConnectionProvider` itself. See <>. - `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] From 952bf6f9c0f85b30ba7fe6345bad581959f5d7e8 Mon Sep 17 00:00:00 2001 From: Elena Felder <41136058+elefeint@users.noreply.github.com> Date: Tue, 28 May 2019 12:49:33 -0400 Subject: [PATCH 68/99] HHH-13412 Added link for predefined properties, warning that not all settings apply to all situations --- .../asciidoc/userguide/chapters/jdbc/Database_Access.adoc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 5be8b9baaef0..190c9b23e5d2 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/jdbc/Database_Access.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/jdbc/Database_Access.adoc @@ -43,7 +43,9 @@ Otherwise, the no-arg form is used. === Driver Configuration `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"):: All such setting names 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 +`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 From c94117cedccc107730f7fc6a98242f6ea691a7fd Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Fri, 23 Aug 2019 15:10:02 +0100 Subject: [PATCH 69/99] HHH-13595 Deprecate ConnectionObserver --- .../hibernate/internal/FastSessionServices.java | 6 +++--- .../hibernate/internal/JdbcObserverImpl.java | 17 +++++------------ .../resource/jdbc/spi/JdbcObserver.java | 3 +++ 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java b/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java index e1688c329307..055770581341 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/FastSessionServices.java @@ -125,7 +125,7 @@ final class FastSessionServices { private final Dialect dialect; private final CacheStoreMode defaultCacheStoreMode; private final CacheRetrieveMode defaultCacheRetrieveMode; - private List defaultJdbcObservers; + private final ConnectionObserverStatsBridge defaultJdbcObservers; FastSessionServices(SessionFactoryImpl sf) { Objects.requireNonNull( sf ); @@ -174,7 +174,7 @@ final class FastSessionServices { this.defaultCacheRetrieveMode = determineCacheRetrieveMode( defaultSessionProperties ); this.initialSessionCacheMode = CacheModeHelper.interpretCacheMode( defaultCacheStoreMode, defaultCacheRetrieveMode ); this.discardOnClose = sessionFactoryOptions.isReleaseResourcesOnCloseEnabled(); - this.defaultJdbcObservers = Collections.singletonList( new ConnectionObserverStatsBridge( sf ) ); + this.defaultJdbcObservers = new ConnectionObserverStatsBridge( sf ); this.defaultSessionEventListeners = sessionFactoryOptions.getBaselineSessionEventsListenerBuilder(); this.defaultLockOptions = initializeDefaultLockOptions( defaultSessionProperties ); } @@ -273,7 +273,7 @@ private static CacheStoreMode determineCacheStoreMode(Map settin return ( CacheStoreMode ) settings.get( JPA_SHARED_CACHE_STORE_MODE ); } - public Iterable getDefaultJdbcObservers() { + public ConnectionObserverStatsBridge getDefaultJdbcObserver() { return defaultJdbcObservers; } 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 be3cf12978e3..01f2eb71b426 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/JdbcObserverImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/JdbcObserverImpl.java @@ -8,7 +8,6 @@ import java.sql.Connection; -import org.hibernate.engine.jdbc.spi.ConnectionObserver; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.resource.jdbc.spi.JdbcObserver; @@ -18,11 +17,11 @@ public class JdbcObserverImpl implements JdbcObserver { private final SharedSessionContractImplementor session; - private final Iterable observers; + private final ConnectionObserverStatsBridge observer; public JdbcObserverImpl(SharedSessionContractImplementor session, FastSessionServices fastSessionServices) { this.session = session; - this.observers = fastSessionServices.getDefaultJdbcObservers(); + this.observer = fastSessionServices.getDefaultJdbcObserver(); } @Override @@ -32,9 +31,7 @@ public void jdbcConnectionAcquisitionStart() { @Override public void jdbcConnectionAcquisitionEnd(Connection connection) { - for ( ConnectionObserver observer : observers ) { - observer.physicalConnectionObtained( connection ); - } + observer.physicalConnectionObtained( connection ); } @Override @@ -44,9 +41,7 @@ public void jdbcConnectionReleaseStart() { @Override public void jdbcConnectionReleaseEnd() { - for ( ConnectionObserver observer : observers ) { - observer.physicalConnectionReleased(); - } + observer.physicalConnectionReleased(); } @Override @@ -56,9 +51,7 @@ public void jdbcPrepareStatementStart() { @Override public void jdbcPrepareStatementEnd() { - for ( ConnectionObserver observer : observers ) { - observer.statementPrepared(); - } + observer.statementPrepared(); session.getEventListenerManager().jdbcPrepareStatementEnd(); } 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 49aa95db793d..124d28921dda 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 @@ 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); From db92f7aa00ff32dee1c59dbb3ee764241f51d582 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Fri, 23 Aug 2019 15:16:52 +0100 Subject: [PATCH 70/99] HHH-13595 Small tuning of JdbcObserverImpl --- .../hibernate/internal/JdbcObserverImpl.java | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) 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 01f2eb71b426..015aca438526 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/JdbcObserverImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/JdbcObserverImpl.java @@ -8,25 +8,27 @@ import java.sql.Connection; +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 { +public final class JdbcObserverImpl implements JdbcObserver { - private final SharedSessionContractImplementor session; private final ConnectionObserverStatsBridge observer; + private final SessionEventListenerManager eventListenerManager; + private final SharedSessionContractImplementor session; public JdbcObserverImpl(SharedSessionContractImplementor session, FastSessionServices fastSessionServices) { this.session = session; this.observer = fastSessionServices.getDefaultJdbcObserver(); + this.eventListenerManager = session.getEventListenerManager(); } @Override public void jdbcConnectionAcquisitionStart() { - } @Override @@ -36,7 +38,6 @@ public void jdbcConnectionAcquisitionEnd(Connection connection) { @Override public void jdbcConnectionReleaseStart() { - } @Override @@ -46,33 +47,33 @@ public void jdbcConnectionReleaseEnd() { @Override public void jdbcPrepareStatementStart() { - session.getEventListenerManager().jdbcPrepareStatementStart(); + eventListenerManager.jdbcPrepareStatementStart(); } @Override public void jdbcPrepareStatementEnd() { observer.statementPrepared(); - session.getEventListenerManager().jdbcPrepareStatementEnd(); + 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 From 44c6f0fa3f52a2959a6525421954c68b0b34b042 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Fri, 23 Aug 2019 15:20:02 +0100 Subject: [PATCH 71/99] HHH-13595 Remove the invocations to the no-op methods of ConnectionObserver --- .../resource/jdbc/internal/LogicalConnectionManagedImpl.java | 3 --- .../resource/jdbc/internal/ResourceRegistryStandardImpl.java | 4 ---- 2 files changed, 7 deletions(-) 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 2a4cb84d5db6..3486a39fc260 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 @@ -100,7 +100,6 @@ private LogicalConnectionManagedImpl( private Connection acquireConnectionIfNeeded() { if ( physicalConnection == null ) { // todo : is this the right place for these observer calls? - observer.jdbcConnectionAcquisitionStart(); try { physicalConnection = jdbcConnectionAccess.obtainConnection(); } @@ -186,8 +185,6 @@ private void releaseConnection() { return; } - // todo : is this the right place for these observer calls? - observer.jdbcConnectionReleaseStart(); try { if ( !physicalConnection.isClosed() ) { sqlExceptionHelper.logAndClearWarnings( physicalConnection ); 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 492a53c811b8..3f3af3b97fcc 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 @@ -361,10 +361,6 @@ public void releaseResources() { } ); nclobs = null; } - - if ( jdbcObserver != null ) { - jdbcObserver.jdbcReleaseRegistryResourcesEnd(); - } } private boolean hasRegistered(final HashMap resource) { From 5c95096e7cc10a23f50e9654851e3b9fc6ee692a Mon Sep 17 00:00:00 2001 From: Chris Cranford Date: Fri, 23 Aug 2019 15:04:52 -0400 Subject: [PATCH 72/99] HHH-13564 - Fix NullPointerException for audited entity with embedded-id in mapping superclass that makes use of generics. --- .../entities/mapper/id/EmbeddedIdMapper.java | 4 +- .../embeddedid/EmbeddedIdGenericsTest.java | 219 ++++++++++++++++++ 2 files changed, 220 insertions(+), 3 deletions(-) create mode 100644 hibernate-envers/src/test/java/org/hibernate/envers/test/integration/ids/embeddedid/EmbeddedIdGenericsTest.java 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 ed62002ccd1c..abbc84872a3d 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 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 boolean mapToEntityFromMap(final Object obj, final Map data) { 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 000000000000..4f68b065a66d --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/ids/embeddedid/EmbeddedIdGenericsTest.java @@ -0,0 +1,219 @@ +/* + * 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 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") + 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 ); + } + } +} From bcbfea7724a8f2dd14d56e9a7708d4e9680b591a Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Tue, 27 Aug 2019 18:11:13 +0100 Subject: [PATCH 73/99] HHH-13599 Avoid ArrayList allocation in JtaTransactionCoordinatorImp in common scenario --- .../JtaTransactionCoordinatorImpl.java | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) 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 b2f3a59eae7b..13f2edaba562 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 JtaTransactionCoordinatorImpl( 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 JtaTransactionCoordinatorImpl( /** * 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 void afterCompletion(boolean successful, boolean delayed) { } 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 ); + } } /** From 3d715c0ae8879a1cdc202f52ee3f0cc6c582e005 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Wed, 28 Aug 2019 11:08:01 +0100 Subject: [PATCH 74/99] HHH-13600 Extract Alias allocation from loop in AbstractCollectionPersister --- .../persister/collection/AbstractCollectionPersister.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 42380d1fe7bd..e7f9e2de1b21 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 String[] getCollectionPropertyColumnAliases(String propertyName, String s } 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; } From 86e498f4f7bd4b2f8ecc6f02d57844f344209c60 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Wed, 28 Aug 2019 11:15:09 +0100 Subject: [PATCH 75/99] HHH-13600 Avoid capturing lambdas in AbstractDomainDataRegion construction --- .../spi/support/AbstractDomainDataRegion.java | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) 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 a76eacfc1c6a..568757c580f2 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 CollectionDataAccess getCollectionDataAccess(NavigableRole collectionRole 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 @@ private Map generateEntityDataAccessMap( } 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 @@ private Map generateNaturalIdDataAccessMap(D 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 ) ); } From 9f71bd890d32bbcc02e0e8572cb632a8975e938c Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Wed, 28 Aug 2019 11:21:04 +0100 Subject: [PATCH 76/99] HHH-13600 Unguarded log statement in CollectionCacheInvalidator --- .../hibernate/cache/internal/CollectionCacheInvalidator.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 e3d35c0b7e09..81793fb23d46 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 @@ private void evictCache(Object entity, EntityPersister persister, EventSource se 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 @@ private void evictCache(Object entity, EntityPersister persister, EventSource se } } 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) -> { From c8a36cd961464797c6277449d7f640af05c89e8d Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Wed, 28 Aug 2019 11:26:18 +0100 Subject: [PATCH 77/99] HHH-13600 Extracting some more constants from loops --- .../hql/internal/ast/exec/DeleteExecutor.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) 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 849e5c799168..a90422009849 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.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 DeleteExecutor(HqlSqlWalker walker, Queryable persister) { 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 DeleteExecutor(HqlSqlWalker walker, Queryable persister) { 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() ); From 40b30fa0998e521f68c837dc897e53b4f75f87b2 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Wed, 28 Aug 2019 11:39:46 +0100 Subject: [PATCH 78/99] HHH-13600 Avoid capturing lambdas in ParameterTranslationsImpl --- .../ast/ParameterTranslationsImpl.java | 34 ++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) 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 efefa8c72b51..c430dc604e12 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 @@ else if ( NamedParameterSpecification.class.isInstance( specification ) ) { } 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 @@ else if ( NamedParameterSpecification.class.isInstance( specification ) ) { } } + 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() { From ea301a7084fbbde81b749e64908369aa382d0962 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Mon, 2 Sep 2019 13:52:29 +0100 Subject: [PATCH 79/99] HHH-13564 - Fix EmbeddedIdGenericsTest failures on Oracle and MariaDB --- .../test/integration/ids/embeddedid/EmbeddedIdGenericsTest.java | 2 ++ 1 file changed, 2 insertions(+) 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 index 4f68b065a66d..67de748ab977 100644 --- 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 @@ -18,6 +18,7 @@ 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; @@ -105,6 +106,7 @@ public int hashCode() { @Audited @Entity(name = "Trigger") + @Table(name = "`Trigger`") public static class Trigger extends CompositeIdBaseEntity { private boolean active; From 11409ed633e62f507d54d8a819dd6a3326afe982 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Sun, 25 Aug 2019 22:31:50 -0700 Subject: [PATCH 80/99] HHH-13590 : test cases --- ...cadeMergeToProxyEntityCopyAllowedTest.java | 315 ++++++++++++++++++ .../CascadeMergeToProxySimpleTest.java | 167 ++++++++++ 2 files changed, 482 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/cascade/CascadeMergeToProxyEntityCopyAllowedTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/cascade/CascadeMergeToProxySimpleTest.java 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 000000000000..0493ca0bcaff --- /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 000000000000..972ad2aebce3 --- /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 ); + } + + } +} From 148b4b2ed009b488206b8e3854bc7270dc7931b4 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Sun, 25 Aug 2019 22:32:31 -0700 Subject: [PATCH 81/99] HHH-13590 : TransientObjectException merging a non-proxy association to a proxy --- .../org/hibernate/event/internal/DefaultMergeEventListener.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 56326d172a51..39f1f47b764b 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 @@ private Object unproxyManagedForDetachedMerging( Object managed, EntityPersister persister, EventSource source) { - if ( incoming instanceof HibernateProxy ) { + if ( managed instanceof HibernateProxy ) { return source.getPersistenceContextInternal().unproxy( managed ); } From 650b1dec6a105a40f197cececd754f24cdca7e57 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Fri, 30 Aug 2019 19:02:44 -0700 Subject: [PATCH 82/99] HHH-13607 : Added tests --- ...turalIdInUninitializedAssociationTest.java | 173 ++++++++++++++++++ .../NaturalIdInUninitializedProxyTest.java | 150 +++++++++++++++ ...turalIdInUninitializedAssociationTest.java | 173 ++++++++++++++++++ .../NaturalIdInUninitializedProxyTest.java | 144 +++++++++++++++ 4 files changed, 640 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/NaturalIdInUninitializedAssociationTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/NaturalIdInUninitializedProxyTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/naturalid/lazy/NaturalIdInUninitializedAssociationTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/naturalid/lazy/NaturalIdInUninitializedProxyTest.java 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 000000000000..1f86b4aa83b3 --- /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/proxy/NaturalIdInUninitializedProxyTest.java b/hibernate-core/src/test/java/org/hibernate/test/bytecode/enhancement/lazy/proxy/NaturalIdInUninitializedProxyTest.java new file mode 100644 index 000000000000..37f677804e94 --- /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/naturalid/lazy/NaturalIdInUninitializedAssociationTest.java b/hibernate-core/src/test/java/org/hibernate/test/naturalid/lazy/NaturalIdInUninitializedAssociationTest.java new file mode 100644 index 000000000000..f17d4baed4a8 --- /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 000000000000..b73ce854f75b --- /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; + } + } + +} From 7ceaf3aaa42992b18f809adc2322388a81018fc2 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Wed, 4 Sep 2019 16:42:10 -0500 Subject: [PATCH 83/99] HHH-13607 - Exception thrown while flushing uninitialized enhanced proxy with immutable natural ID --- .../DefaultFlushEntityEventListener.java | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEntityEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultFlushEntityEventListener.java index 1e60d6a5d78d..1f0a6049025e 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.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 void checkId(Object object, EntityPersister persister, Serializable id, S 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 @@ private void checkNaturalId( 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 @@ else if ( !mightBeDirty && loadedState != null ) { // grab its current state values = persister.getPropertyValues( entity ); - checkNaturalId( persister, entry, values, loadedState, session ); + checkNaturalId( persister, entity, entry, values, loadedState, session ); } return values; } From 1cb81c0304b6ed8f521054245abf69ab03fb3357 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Fri, 23 Aug 2019 22:39:50 -0700 Subject: [PATCH 84/99] HHH-13586 : test case --- .../SharedDomainDataAndQueryResultsTest.java | 435 ++++++++++++++++++ 1 file changed, 435 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/cache/SharedDomainDataAndQueryResultsTest.java 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 000000000000..5de7a14cb6ab --- /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() { + } + } +} From 2076c68ddff5dc39055e90e162a34c99c72261cb Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Fri, 23 Aug 2019 22:40:36 -0700 Subject: [PATCH 85/99] HHH-13586 : ClassCastException when using a single region name for both entity and query results --- .../cache/internal/EnabledCaching.java | 40 +++++++++++++------ .../hibernate/cache/spi/CacheImplementor.java | 4 +- 2 files changed, 30 insertions(+), 14 deletions(-) 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 e7257ed0d046..9ce34807a33b 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 @@ /** * @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 TimestampsCache getTimestampsCache() { @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 QueryResultsCache getQueryResultsCacheStrictly(String regionName) { } 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 @@ protected QueryResultsCache makeQueryResultsRegionAccess(String regionName) { } 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 Set getCacheRegionNames() { @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 void close() { 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 00a28c7b99d0..c77b13db7b59 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 From b26ec4e6255a9e17dda7dfbc299d1f37ad51d136 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Tue, 3 Sep 2019 14:43:37 -0700 Subject: [PATCH 86/99] HHH-13611 : Restore EntityMetamodel constructor to take SessionFactoryImplementor argument instead of PersisterCreationContext --- .../spi/interceptor/EnhancementHelper.java | 5 +-- .../interceptor/LazyAttributesMetadata.java | 7 ++-- .../entity/AbstractEntityPersister.java | 20 ++---------- .../org/hibernate/tuple/PropertyFactory.java | 7 ++-- .../BytecodeEnhancementMetadataPojoImpl.java | 6 ++-- .../tuple/entity/EntityMetamodel.java | 32 +++---------------- .../test/legacy/CustomPersister.java | 2 +- 7 files changed, 16 insertions(+), 63 deletions(-) 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 91d865ac49ad..7c509cfe324d 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 @@ 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 static boolean includeInBaseFetchGroup( } // 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 380b17711ce1..8192d3d13293 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.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 static LazyAttributesMetadata from( 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/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index d2b78ee95fb2..1a3265db1fdd 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 @@ -568,7 +568,7 @@ public AbstractEntityPersister( this.naturalIdRegionAccessStrategy = null; } - this.entityMetamodel = new EntityMetamodel( persistentClass, this, creationContext ); + this.entityMetamodel = new EntityMetamodel( persistentClass, this, creationContext.getSessionFactory() ); this.entityTuplizer = this.entityMetamodel.getTuplizer(); if ( entityMetamodel.isMutable() ) { @@ -704,14 +704,7 @@ public 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 ) { @@ -787,14 +780,7 @@ public 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(); diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/PropertyFactory.java b/hibernate-core/src/main/java/org/hibernate/tuple/PropertyFactory.java index a8004fa92fff..779c114e706e 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/PropertyFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/PropertyFactory.java @@ -7,7 +7,6 @@ package org.hibernate.tuple; import java.lang.reflect.Constructor; -import java.util.function.Function; import org.hibernate.EntityMode; import org.hibernate.HibernateException; @@ -155,8 +154,7 @@ public static NonIdentifierAttribute buildEntityBasedAttribute( SessionFactoryImplementor sessionFactory, int attributeNumber, Property property, - boolean lazyAvailable, - Function hasSubclassChecker) { + boolean lazyAvailable) { final Type type = property.getValue().getType(); final NonIdentifierAttributeNature nature = decode( type ); @@ -174,8 +172,7 @@ public static NonIdentifierAttribute buildEntityBasedAttribute( 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 390db7b17988..cad2b64c9fad 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 @@ 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 static BytecodeEnhancementMetadata from( 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 0e4de4531e1e..298cd244821a 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.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 EntityMetamodel( 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 EntityMetamodel( 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 EntityMetamodel( 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 ) { 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 d79fa014e8ce..833cf33961db 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 CustomPersister( NaturalIdDataAccess naturalIdRegionAccessStrategy, PersisterCreationContext creationContext) { this.factory = creationContext.getSessionFactory(); - this.entityMetamodel = new EntityMetamodel( model, this, creationContext ); + this.entityMetamodel = new EntityMetamodel( model, this, creationContext.getSessionFactory() ); } public boolean hasLazyProperties() { From ea8694ad38a3cc06972523e46e35317ad4f4f9b9 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Mon, 9 Sep 2019 10:32:17 +0100 Subject: [PATCH 87/99] HHH-13611 : Restore EntityMetamodel constructor to take SessionFactoryImplementor argument instead of PersisterCreationContext --- .../org/hibernate/persister/entity/AbstractEntityPersister.java | 2 +- .../test/java/org/hibernate/test/legacy/CustomPersister.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 1a3265db1fdd..7fea63ab5092 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 @@ -568,7 +568,7 @@ public AbstractEntityPersister( this.naturalIdRegionAccessStrategy = null; } - this.entityMetamodel = new EntityMetamodel( persistentClass, this, creationContext.getSessionFactory() ); + this.entityMetamodel = new EntityMetamodel( persistentClass, this, factory ); this.entityTuplizer = this.entityMetamodel.getTuplizer(); if ( entityMetamodel.isMutable() ) { 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 833cf33961db..9615248f4f16 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 CustomPersister( NaturalIdDataAccess naturalIdRegionAccessStrategy, PersisterCreationContext creationContext) { this.factory = creationContext.getSessionFactory(); - this.entityMetamodel = new EntityMetamodel( model, this, creationContext.getSessionFactory() ); + this.entityMetamodel = new EntityMetamodel( model, this, factory ); } public boolean hasLazyProperties() { From f0218e82adaf96fb5b20017a6845d0fabecfb5dc Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Tue, 10 Sep 2019 15:51:11 +0100 Subject: [PATCH 88/99] HHH-13616 Enable the hibernate-orm-modules test for JDK 11 --- build.gradle | 2 +- gradle/libraries.gradle | 6 +++--- gradle/wrapper/gradle-wrapper.jar | Bin 56172 -> 56177 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- .../hibernate-orm-modules.gradle | 9 ++++----- .../HibernateEnversOnWildflyTest.java | 2 ++ .../HibernateModulesOnWildflyTest.java | 2 ++ .../TransactionRollbackTest.java | 1 + .../AuditedEntity.java | 2 +- .../Kryptonite.java | 2 +- 10 files changed, 16 insertions(+), 12 deletions(-) rename hibernate-orm-modules/src/test/java/org/hibernate/wildfly/{integrationtest => model}/AuditedEntity.java (96%) rename hibernate-orm-modules/src/test/java/org/hibernate/wildfly/{integrationtest => model}/Kryptonite.java (90%) diff --git a/build.gradle b/build.gradle index 2416a7aedf3e..7eb33121c974 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/gradle/libraries.gradle b/gradle/libraries.gradle index 4fbbdce617e8..98fa8af98c73 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' diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 28861d273a5d270fd8f65dd74570c17c9c507736..94336fcae912db8a11d55634156fa011f4686124 100644 GIT binary patch delta 692 zcmYk4T}V@59LD$ljeUf+K{mFtg;7@>Yq^4!Io(R7$e0sDrO|(Ka zAd0cFi^%R0#%_X^!lG@SxeqtD^>I-IyO3~nThQ_4w0z<4{+{1+&VfJgyA^L~#9P7+ zoxbn&+g?vz^P9nxt3WZr!<$}hFc56crP8mWM+UC)f}^G13t>lf<}otyw#W>u8H(R zrzkB>XN?H12~;&TmELSbw`4$F)+saBX6X`@56Ni3ywt}cNJ!)K*~Yu$*>3u9g5w<- z?>RV1SqC+SICfla<^Nmq7+U4Aloj>xGP~Y}ipPY|x=1g*is~iR1V`J%LAY`v*(Q0M z&kfLCpMn~=eEYDS6IlS3yqA52KkuZrX)OEAa7_#Nk{9WTUyi*p8ZcH6*fq=)rdR{H z!WfO*LcU-{y=sN8DA0>Jj8u-`dC|^7$QN&tBf!PfhLeun<(SwQq|kkiM@z%hALJMa zTJf-KK&m8AJj|80JV-Hc+{R8TQsS`^)%adMOQomHcyY6bijij2SJa4Yc2V)!F6CxP zcq`YL6>lnjy82444#QpTV*Qw@j?%&^qN_G|iw5ke3AB{NO47-P_SXb<8xLwX*f4(9 XPFi-~FVnxH;QJzaK6R>pr*^_$Jgg(_ delta 717 zcmYk4Ur19?9LMkZS?G# zyeoQy%FYN}jB3ywJwTeXYg)_NVUD?31}(7~mW!vcCc4**XE7rp%_VTfRXWvzWv>Z{ zAEc#Lfi>}+*p^VJ{)*6}i7GaLn~4VMYU6~V735g433OwW4+ZcRC97#vG+J>{U6_XU1QRa4x{u^LSuRf;|o zsi7|q. */ -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 7b073fe6f3da..3ab61e224b39 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; From be7cc76556f1a2efd715b24ba4516fba0ddbbec1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Mon, 26 Aug 2019 13:53:29 +0200 Subject: [PATCH 89/99] HHH-13582 Upgrade MySQL Connector/J to 8.0.17 ConnectorJ 8 is the version used in WildFly integration tests. ConnectorJ 5 is apparently no longer tested. Note this solves most timezone-related issues we've been having. --- gradle/libraries.gradle | 3 +-- .../hibernate/test/annotations/EntityTest.java | 6 ++---- .../autocommit/MySQLSkipAutoCommitTest.java | 17 +++++++++++++---- .../test/temporal/TimePropertyTest.java | 7 ++++++- .../databases/mysql56/matrix.gradle | 2 +- .../databases/mysql8/matrix.gradle | 2 +- 6 files changed, 24 insertions(+), 13 deletions(-) diff --git a/gradle/libraries.gradle b/gradle/libraries.gradle index 98fa8af98c73..16d24e151b62 100644 --- a/gradle/libraries.gradle +++ b/gradle/libraries.gradle @@ -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', 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 afaf60ebd8cf..5dd684f87056 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.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 void testNonGetter() throws Exception { @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/resource/transaction/jdbc/autocommit/MySQLSkipAutoCommitTest.java b/hibernate-core/src/test/java/org/hibernate/test/resource/transaction/jdbc/autocommit/MySQLSkipAutoCommitTest.java index b8f61a87f7ba..a417dc99fb9b 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 @@ protected DataSource dataSource() { 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/temporal/TimePropertyTest.java b/hibernate-core/src/test/java/org/hibernate/test/temporal/TimePropertyTest.java index 935d011c143c..d77d1228614f 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 @@ 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-spatial/databases/mysql56/matrix.gradle b/hibernate-spatial/databases/mysql56/matrix.gradle index 68f6ea4f81c6..361a524b53f7 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 8ddca02827df..361a524b53f7 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" From a580227569fd4ec362b5d1b387801eabd8b24f35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Tue, 27 Aug 2019 11:47:07 +0200 Subject: [PATCH 90/99] HHH-13582 Ignore LocalDateTest for MySQL 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 --- .../test/java/org/hibernate/test/type/LocalDateTest.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) 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 50a00f62c221..5f370c8b02dd 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 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 @@ * 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 { From f9f6793232d5f5144f63622bf1ee1710b42ee74c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Mon, 26 Aug 2019 18:11:57 +0200 Subject: [PATCH 91/99] HHH-13580 Fix a copy/paste error in OffsetTimeTest --- .../src/test/java/org/hibernate/test/type/OffsetTimeTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 1d12f7f9ff5d..359742e57b50 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 @@ -34,7 +34,7 @@ import org.junit.runners.Parameterized; /** - * Tests for storage of LocalTime properties. + * Tests for storage of OffsetTime properties. */ public class OffsetTimeTest extends AbstractJavaTimeTypeTest { From 8fce51c765311302305e52669ca778272e650144 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Mon, 26 Aug 2019 11:44:53 +0200 Subject: [PATCH 92/99] HHH-13580 Use safer ZoneId => TimeZone conversion in AbstractJavaTimeTypeTest We were testing GMT instead of UTC-8 without even knowing it... --- .../test/type/AbstractJavaTimeTypeTest.java | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) 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 69384e16751e..8baef2765771 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 @@ public void nativeWriteThenRead() { 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 @@ else if ( cause instanceof Error ) { } } + 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; } From 74d35b2d59c4fff09f40df6d393800c88dfdda7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Mon, 26 Aug 2019 13:31:17 +0200 Subject: [PATCH 93/99] HHH-13580 Ignore LocalTimeTest and OffsetTimeTest's 'nativeWriteThenRead' test for MySQL The returned time is right (otherwise #writeThenRead would fail), it's just that a different day is returned, but it won't affect the LocalTime representation manipulated by the user. --- .../test/java/org/hibernate/test/type/LocalTimeTest.java | 7 +++++++ .../test/java/org/hibernate/test/type/OffsetTimeTest.java | 7 +++++++ 2 files changed, 14 insertions(+) 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 e4139ed48c84..36bceb0235f5 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 @@ -23,6 +23,7 @@ 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; @@ -186,6 +187,12 @@ protected Object getActualJdbcValue(ResultSet resultSet, int columnIndex) throws @Override @Test @SkipForDialect(value = AbstractHANADialect.class, comment = "HANA seems to return a java.sql.Timestamp instead of a java.sql.Time") + @SkipForDialect(value = MySQL5Dialect.class, + comment = "HHH-13580 MySQL seems to store the whole timestamp, not just the time," + + " which for some timezones results in a date other than 1970-01-01 being returned" + + " (typically 1969-12-31), even though the time is always right." + + " Since java.sql.Time holds the whole timestamp, not just the time," + + " its equals() method ends up returning false in this test.") public void writeThenNativeRead() { super.writeThenNativeRead(); } 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 359742e57b50..cb83f66941b7 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 @@ -25,6 +25,7 @@ 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; @@ -215,6 +216,12 @@ protected Object getActualJdbcValue(ResultSet resultSet, int columnIndex) throws @Override @Test @SkipForDialect(value = AbstractHANADialect.class, comment = "HANA seems to return a java.sql.Timestamp instead of a java.sql.Time") + @SkipForDialect(value = MySQL5Dialect.class, + comment = "HHH-13580 MySQL seems to store the whole timestamp, not just the time," + + " which for some timezones results in a date other than 1970-01-01 being returned" + + " (typically 1969-12-31), even though the time is always right." + + " Since java.sql.Time holds the whole timestamp, not just the time," + + " its equals() method ends up returning false in this test.") public void writeThenNativeRead() { super.writeThenNativeRead(); } From debc5d37f8c12d7fb93633c953d672608eea3f13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Mon, 26 Aug 2019 16:29:34 +0200 Subject: [PATCH 94/99] HHH-13581 Disable tests involving MariaDB ConnectorJ's buggy ResultSet#getTime(int, Calendar) method --- .../java/org/hibernate/test/type/LocalTimeTest.java | 13 +++++++++++++ .../org/hibernate/test/type/OffsetTimeTest.java | 13 +++++++++++++ 2 files changed, 26 insertions(+) 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 36bceb0235f5..73a5ad6b70b1 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.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; @@ -56,6 +57,18 @@ public ParametersBuilder addPersistedWithoutHibernate(int yearWhenPersistedWitho monthWhenPersistedWithoutHibernate, dayWhenPersistedWithoutHibernate ); } + + @Override + protected Iterable 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}") 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 cb83f66941b7..16bef9c23717 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.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; @@ -59,6 +60,18 @@ public ParametersBuilder addPersistedWithoutHibernate(int yearWhenPersistedWitho monthWhenPersistedWithoutHibernate, dayWhenPersistedWithoutHibernate ); } + + @Override + protected Iterable 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}") From ef87991fa39478234bcbc5b274eafbf4aadcbc9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Fri, 30 Aug 2019 14:42:01 +0200 Subject: [PATCH 95/99] HHH-13605 Upgrade the MariaDB JDBC driver to 2.2.4 in the matrix_mariadb task It's already the version we use when running test -Pdb=mariadb, so let's be consistent. --- databases/mariadb/matrix.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/databases/mariadb/matrix.gradle b/databases/mariadb/matrix.gradle index 7ebdee14585b..39b03e208f25 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 From 26c2e0bf2b4d714cad340f3ebb3b18a498095168 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Fri, 13 Sep 2019 11:09:25 +0100 Subject: [PATCH 96/99] HHH-13621 Add test for issue --- ...ctionPropertyValueEndingWithSpaceTest.java | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/tool/schema/SchemaGenetationSciptsActionPropertyValueEndingWithSpaceTest.java 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 000000000000..70b35aaacbf1 --- /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() { + } +} From f5877052ff00cf923628d0c1859cc1f7779df500 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Fri, 13 Sep 2019 11:24:11 +0100 Subject: [PATCH 97/99] HHH-13621 Exception if spaces after value of javax.persistence.schema-generation.scripts.action in hibernate.properties --- .../src/main/java/org/hibernate/tool/schema/Action.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/Action.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/Action.java index c7c5b375f547..8265628b3224 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/Action.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/Action.java @@ -101,7 +101,7 @@ public static Action interpretJpaSetting(Object value) { return (Action) value; } - final String name = value.toString(); + final String name = value.toString().trim(); if ( name.isEmpty() || NONE.externalJpaName.equals( name ) ) { // default is NONE return NONE; From 57dbfbb60e1a97708a0a4f004fc3f4101d86ef97 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Mon, 16 Sep 2019 16:22:56 +0100 Subject: [PATCH 98/99] HHH-13622 Upgrade the WildFly Transaction Client to 1.1.7.Final --- gradle/libraries.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libraries.gradle b/gradle/libraries.gradle index 16d24e151b62..2b0a94227e21 100644 --- a/gradle/libraries.gradle +++ b/gradle/libraries.gradle @@ -158,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' From 0f2a037e1880a074b6328ea3f2bcd86a161469cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Fri, 30 Aug 2019 14:57:19 +0200 Subject: [PATCH 99/99] HHH-13606 Upgrade the HANA JDBC driver to 2.4.59 in the matrix_hana task It's already the version we use when running test -Pdb=hana, so let's be consistent. --- databases/hana/matrix.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/databases/hana/matrix.gradle b/databases/hana/matrix.gradle index 8782b540d708..59d7bc269ecb 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