From de3f3c1d74f43692e7bf50f7cf967406db1535ab Mon Sep 17 00:00:00 2001 From: Francois van Delft Date: Mon, 8 Feb 2021 12:17:38 +0100 Subject: [PATCH 01/18] HHH-14443 Add hashcode to ObjectTypeCacheEntry, so query cache can do a successfull lookup for queryies with AnyTypes --- .../src/main/java/org/hibernate/type/AnyType.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/hibernate-core/src/main/java/org/hibernate/type/AnyType.java b/hibernate-core/src/main/java/org/hibernate/type/AnyType.java index 0b51612358..2c56bc2694 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/AnyType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/AnyType.java @@ -13,6 +13,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.Arrays; import java.util.Map; +import java.util.Objects; import java.util.Set; import org.hibernate.EntityMode; @@ -519,5 +520,18 @@ public class AnyType extends AbstractType implements CompositeType, AssociationT this.entityName = entityName; this.id = id; } + + public int hashCode() { + return Objects.hash(entityName, id); + } + + public boolean equals(Object object) { + if (object instanceof ObjectTypeCacheEntry) { + ObjectTypeCacheEntry objectTypeCacheEntry = (ObjectTypeCacheEntry)object; + return Objects.equals(objectTypeCacheEntry.entityName, entityName) && Objects.equals(objectTypeCacheEntry.id, id); + } + return false; + } + } } From 6868c68278b1b93d0ac56fc8d57d1b31bee295fe Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Mon, 1 Mar 2021 16:13:02 +0000 Subject: [PATCH 02/18] HHH-14443 Formatting and style fixes --- .../src/main/java/org/hibernate/type/AnyType.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/type/AnyType.java b/hibernate-core/src/main/java/org/hibernate/type/AnyType.java index 2c56bc2694..b321f2cff5 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/AnyType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/AnyType.java @@ -522,13 +522,13 @@ public class AnyType extends AbstractType implements CompositeType, AssociationT } public int hashCode() { - return Objects.hash(entityName, id); + return Objects.hash( entityName, id ); } public boolean equals(Object object) { - if (object instanceof ObjectTypeCacheEntry) { + if (object instanceof ObjectTypeCacheEntry) { ObjectTypeCacheEntry objectTypeCacheEntry = (ObjectTypeCacheEntry)object; - return Objects.equals(objectTypeCacheEntry.entityName, entityName) && Objects.equals(objectTypeCacheEntry.id, id); + return Objects.equals( objectTypeCacheEntry.entityName, entityName ) && Objects.equals( objectTypeCacheEntry.id, id ); } return false; } From 4fad616d4bfe144349cabe055229dc54e53f3c08 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Sun, 14 Feb 2021 01:42:49 +0100 Subject: [PATCH 03/18] HHH-14474 Refactor internal visibility to allow Hibernate Reactive to implement non-primary key associations see https://github.com/hibernate/hibernate-reactive/issues/565 --- .../hibernate/loader/entity/AbstractEntityLoader.java | 5 +++++ .../java/org/hibernate/loader/entity/EntityLoader.java | 8 ++++++++ .../hibernate/loader/entity/UniqueEntityLoader.java | 7 +++++++ .../persister/entity/AbstractEntityPersister.java | 10 +++++----- 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/AbstractEntityLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/AbstractEntityLoader.java index f0716eae1c..a3d66e6755 100755 --- a/hibernate-core/src/main/java/org/hibernate/loader/entity/AbstractEntityLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/AbstractEntityLoader.java @@ -61,6 +61,11 @@ public abstract class AbstractEntityLoader return load( session, id, optionalObject, id, lockOptions, readOnly ); } + @Override + public Object load(Object id, SharedSessionContractImplementor session, LockOptions lockOptions) { + return load( session, id, null, null, lockOptions, null ); + } + protected Object load( SharedSessionContractImplementor session, Object id, diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/EntityLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/EntityLoader.java index d7be98e426..4515bc6e95 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/entity/EntityLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/EntityLoader.java @@ -172,10 +172,18 @@ public class EntityLoader extends AbstractEntityLoader { } } + /** + * @deprecated will be removed. Use one of the load methods on {@link AbstractEntityLoader} instead. + */ + @Deprecated public Object loadByUniqueKey(SharedSessionContractImplementor session, Object key) { return loadByUniqueKey( session, key, null ); } + /** + * @deprecated will be removed. Use one of the load methods on {@link AbstractEntityLoader} instead. + */ + @Deprecated public Object loadByUniqueKey(SharedSessionContractImplementor session, Object key, Boolean readOnly) { return load( session, key, null, null, LockOptions.NONE, readOnly ); } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/UniqueEntityLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/UniqueEntityLoader.java index 9763703486..87167eb877 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/entity/UniqueEntityLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/UniqueEntityLoader.java @@ -60,4 +60,11 @@ public interface UniqueEntityLoader { Boolean readOnly) { return load( id, optionalObject, session, lockOptions ); } + + default Object load( + Object id, + SharedSessionContractImplementor session, + LockOptions lockOptions) { + throw new UnsupportedOperationException(); + } } 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 e447b10ed2..c01caa20f1 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 @@ -256,7 +256,7 @@ public abstract class AbstractEntityPersister private final EntityLoaderLazyCollection loaders = new EntityLoaderLazyCollection(); - private volatile Map uniqueKeyLoaders; + private volatile Map uniqueKeyLoaders; private volatile Map naturalIdLoaders; // SQL strings @@ -2480,7 +2480,7 @@ public abstract class AbstractEntityPersister Object uniqueKey, SharedSessionContractImplementor session) throws HibernateException { return getAppropriateUniqueKeyLoader( propertyName, session ) - .loadByUniqueKey( session, uniqueKey ); + .load( uniqueKey, session, LockOptions.NONE ); } public Object loadByNaturalId( @@ -2488,7 +2488,7 @@ public abstract class AbstractEntityPersister LockOptions lockOptions, SharedSessionContractImplementor session) throws HibernateException { return getAppropriateNaturalIdLoader( determineValueNullness( naturalIdValues ), lockOptions, session ) - .loadByUniqueKey( session, naturalIdValues ); + .load( naturalIdValues, session, LockOptions.NONE ); } private EntityLoader getAppropriateNaturalIdLoader( @@ -2511,7 +2511,7 @@ public abstract class AbstractEntityPersister && !loadQueryInfluencers.hasEnabledFetchProfiles(); } - private EntityLoader getAppropriateUniqueKeyLoader( + private UniqueEntityLoader getAppropriateUniqueKeyLoader( String propertyName, SharedSessionContractImplementor session) { LoadQueryInfluencers loadQueryInfluencers = session.getLoadQueryInfluencers(); @@ -2560,7 +2560,7 @@ public abstract class AbstractEntityPersister } } - private EntityLoader createUniqueKeyLoader( + protected UniqueEntityLoader createUniqueKeyLoader( Type uniqueKeyType, String[] columns, LoadQueryInfluencers loadQueryInfluencers) { From 17bffb08a5587ea5da3152e28887d2da5e3ad032 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Mon, 1 Mar 2021 20:08:42 +0000 Subject: [PATCH 04/18] HHH-14474 Style and formatting improvements --- .../loader/entity/UniqueEntityLoader.java | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/UniqueEntityLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/UniqueEntityLoader.java index 87167eb877..eb3769f23e 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/entity/UniqueEntityLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/UniqueEntityLoader.java @@ -23,15 +23,16 @@ public interface UniqueEntityLoader { * Load an entity instance. If optionalObject is supplied, * load the entity state into the given (uninitialized) object. * + * @throws HibernateException indicates problem performing the load. + * * @deprecated use {@link #load(java.io.Serializable, Object, SharedSessionContractImplementor, LockOptions)} instead. */ @SuppressWarnings( {"JavaDoc"}) @Deprecated - Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session) throws HibernateException; - - default Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session, Boolean readOnly) throws HibernateException { - return load( id, optionalObject, session ); - } + Object load( + Serializable id, + Object optionalObject, + SharedSessionContractImplementor session); /** * Load an entity instance by id. If optionalObject is supplied (non-null, @@ -52,6 +53,14 @@ public interface UniqueEntityLoader { SharedSessionContractImplementor session, LockOptions lockOptions); + default Object load( + Serializable id, + Object optionalObject, + SharedSessionContractImplementor session, + Boolean readOnly) { + return load( id, optionalObject, session ); + } + default Object load( Serializable id, Object optionalObject, From eb639a2d95cc5d8c5c86f35afe407da6a5dc296a Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Tue, 2 Mar 2021 13:25:59 +0000 Subject: [PATCH 05/18] HHH-14474 Method AbstractEntityPersister#getAppropriateUniqueKeyLoader also need to change in protected --- .../org/hibernate/persister/entity/AbstractEntityPersister.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 c01caa20f1..1a3a43a1c8 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 @@ -2511,7 +2511,7 @@ public abstract class AbstractEntityPersister && !loadQueryInfluencers.hasEnabledFetchProfiles(); } - private UniqueEntityLoader getAppropriateUniqueKeyLoader( + protected UniqueEntityLoader getAppropriateUniqueKeyLoader( String propertyName, SharedSessionContractImplementor session) { LoadQueryInfluencers loadQueryInfluencers = session.getLoadQueryInfluencers(); From 1714c022e271c7faf64b7993de9d06ad457ec4c2 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Tue, 2 Mar 2021 11:36:54 +0100 Subject: [PATCH 06/18] HHH-11076 Log a warning if uninitialized collection unsets session when filters are enabled --- .../collection/internal/AbstractPersistentCollection.java | 4 ++++ .../main/java/org/hibernate/internal/CoreMessageLogger.java | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java index 047f414d44..eee4af75d1 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java @@ -656,6 +656,10 @@ public abstract class AbstractPersistentCollection implements Serializable, Pers LOG.queuedOperationWhenDetachFromSession( collectionInfoString ); } } + if ( allowLoadOutsideTransaction && !initialized && session.getLoadQueryInfluencers().hasEnabledFilters() ) { + final String collectionInfoString = MessageHelper.collectionInfoString( getRole(), getKey() ); + LOG.enabledFiltersWhenDetachFromSession( collectionInfoString ); + } this.session = null; } return true; 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 a95a767ee5..096aa3ec3d 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java @@ -1856,4 +1856,8 @@ public interface CoreMessageLogger extends BasicLogger { @Message(value = "Ignoring ServiceConfigurationError caught while trying to instantiate service '%s'.", id = 505) void ignoringServiceConfigurationError(Class serviceContract, @Cause ServiceConfigurationError error); + @LogMessage(level = WARN) + @Message(value = "Detaching an uninitialized collection with enabled filters from a session: %s", id = 506) + void enabledFiltersWhenDetachFromSession(String collectionInfoString); + } From 59735d2329f4e0c1c829634cd9e561e35212ab88 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Mon, 1 Mar 2021 09:53:43 +0100 Subject: [PATCH 07/18] HHH-14471 Fix concurrency issue due to builder sharing in DynamicBatchingEntityLoader --- .../entity/plan/DynamicBatchingEntityLoader.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/DynamicBatchingEntityLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/DynamicBatchingEntityLoader.java index 6f51a942bd..6bc11cb8cd 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/DynamicBatchingEntityLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/DynamicBatchingEntityLoader.java @@ -27,7 +27,7 @@ public class DynamicBatchingEntityLoader extends BatchingEntityLoader { private static final Logger log = Logger.getLogger( DynamicBatchingEntityLoader.class ); private final int maxBatchSize; - private final EntityLoader.Builder entityLoaderBuilder; + private final LoadQueryInfluencers loadQueryInfluencers; public DynamicBatchingEntityLoader( OuterJoinLoadable persister, @@ -37,10 +37,7 @@ public class DynamicBatchingEntityLoader extends BatchingEntityLoader { LoadQueryInfluencers loadQueryInfluencers) { super( persister ); this.maxBatchSize = maxBatchSize; - - entityLoaderBuilder = EntityLoader.forEntity( persister ) - .withInfluencers( loadQueryInfluencers ) - .withLockOptions( lockOptions ); + this.loadQueryInfluencers = loadQueryInfluencers; } @Override @@ -67,7 +64,12 @@ public class DynamicBatchingEntityLoader extends BatchingEntityLoader { log.debugf( "Batch loading entity: %s", MessageHelper.infoString( persister(), idsToLoad, session.getFactory() ) ); } - final EntityLoader dynamicLoader = entityLoaderBuilder.withBatchSize( idsToLoad.length ).byPrimaryKey(); + + final EntityLoader dynamicLoader = EntityLoader.forEntity( (OuterJoinLoadable) persister() ) + .withInfluencers( loadQueryInfluencers ) + .withLockOptions( lockOptions ) + .withBatchSize( idsToLoad.length ) + .byPrimaryKey(); final List results = dynamicLoader.loadEntityBatch( session, From 2bacaabc378472bd5a4a7341bcb77a50332249ae Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Tue, 23 Feb 2021 18:23:07 -0800 Subject: [PATCH 08/18] HHH-14466 : StackOverflowError loading an entity with eager one-to-many if bidirectional and many-to-one side is the ID --- ...BuildingAssociationVisitationStrategy.java | 63 ++++- ...yToOneEagerDerivedIdFetchModeJoinTest.java | 236 ++++++++++++++++++ 2 files changed, 296 insertions(+), 3 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/annotations/derivedidentities/bidirectional/ManyToOneEagerDerivedIdFetchModeJoinTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/FetchStyleLoadPlanBuildingAssociationVisitationStrategy.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/FetchStyleLoadPlanBuildingAssociationVisitationStrategy.java index 52e3762e7f..923cf41d90 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/FetchStyleLoadPlanBuildingAssociationVisitationStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/FetchStyleLoadPlanBuildingAssociationVisitationStrategy.java @@ -16,13 +16,20 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.internal.CoreLogging; import org.hibernate.loader.plan.spi.CollectionReturn; import org.hibernate.loader.plan.spi.EntityReturn; +import org.hibernate.loader.plan.spi.FetchSource; import org.hibernate.loader.plan.spi.LoadPlan; import org.hibernate.loader.plan.spi.Return; +import org.hibernate.persister.collection.CollectionPersister; +import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.walking.spi.AssociationAttributeDefinition; import org.hibernate.persister.walking.spi.EncapsulatedEntityIdentifierDefinition; import org.hibernate.persister.walking.spi.EntityIdentifierDefinition; import org.hibernate.persister.walking.spi.NonEncapsulatedEntityIdentifierDefinition; import org.hibernate.persister.walking.spi.WalkingException; +import org.hibernate.type.CompositeType; +import org.hibernate.type.EmbeddedComponentType; +import org.hibernate.type.EntityType; +import org.hibernate.type.Type; import org.jboss.logging.Logger; @@ -190,9 +197,59 @@ public class FetchStyleLoadPlanBuildingAssociationVisitationStrategy return new FetchStrategy( fetchStrategy.getTiming(), FetchStyle.SELECT ); } - if ( attributeDefinition.getType().isCollectionType() && isTooManyCollections() ) { - // todo : have this revert to batch or subselect fetching once "sql gen redesign" is in place - return new FetchStrategy( fetchStrategy.getTiming(), FetchStyle.SELECT ); + final FetchSource currentSource = currentSource(); + final Type attributeType = attributeDefinition.getType(); + + + if ( attributeType.isCollectionType() ) { + if ( isTooManyCollections() ) { + // todo : have this revert to batch or subselect fetching once "sql gen redesign" is in place + return new FetchStrategy( fetchStrategy.getTiming(), FetchStyle.SELECT ); + } + if ( currentSource.resolveEntityReference() != null ) { + CollectionPersister collectionPersister = + (CollectionPersister) attributeDefinition.getType().getAssociatedJoinable( sessionFactory() ); + // Check if this is an eager "mappedBy" (inverse) side of a bidirectional + // one-to-many/many-to-one association, with the many-to-one side + // being the associated entity's ID as in: + // + // @Entity + // public class Foo { + // ... + // @OneToMany(mappedBy = "foo", fetch = FetchType.EAGER) + // private Set bars = new HashSet<>(); + // } + // @Entity + // public class Bar implements Serializable { + // @Id + // @ManyToOne(fetch = FetchType.EAGER) + // private Foo foo; + // ... + // } + // + if ( fetchStrategy.getTiming() == FetchTiming.IMMEDIATE && + fetchStrategy.getStyle() == FetchStyle.JOIN && + collectionPersister.isOneToMany() && + collectionPersister.isInverse() ) { + // This is an eager "mappedBy" (inverse) side of a bidirectional + // one-to-many/many-to-one association + final EntityType elementType = (EntityType) collectionPersister.getElementType(); + final Type elementIdType = ( (EntityPersister) elementType.getAssociatedJoinable( sessionFactory() ) ).getIdentifierType(); + if ( elementIdType.isComponentType() && ( (CompositeType) elementIdType ).isEmbedded() ) { + final EmbeddedComponentType elementIdTypeEmbedded = (EmbeddedComponentType) elementIdType; + if ( elementIdTypeEmbedded.getSubtypes().length == 1 && + elementIdTypeEmbedded.getPropertyNames()[ 0 ].equals( collectionPersister.getMappedByProperty() ) ) { + // The associated entity's ID is the other (many-to-one) side of the association. + // The one-to-many side must be set to FetchMode.SELECT; otherwise, + // there will be an infinite loop because the current entity + // would need to be loaded before the associated entity can be loaded, + // but the associated entity cannot be loaded until after the current + // entity is loaded (since the current entity is the associated entity's ID). + return new FetchStrategy( fetchStrategy.getTiming(), FetchStyle.SELECT ); + } + } + } + } } return fetchStrategy; diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/derivedidentities/bidirectional/ManyToOneEagerDerivedIdFetchModeJoinTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/derivedidentities/bidirectional/ManyToOneEagerDerivedIdFetchModeJoinTest.java new file mode 100644 index 0000000000..a8e1b55376 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/derivedidentities/bidirectional/ManyToOneEagerDerivedIdFetchModeJoinTest.java @@ -0,0 +1,236 @@ +/* + * 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.annotations.derivedidentities.bidirectional; + +import java.io.Serializable; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; + +import org.hibernate.Hibernate; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +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.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +public class ManyToOneEagerDerivedIdFetchModeJoinTest extends BaseCoreFunctionalTestCase { + private Foo foo; + + @Test + @TestForIssue(jiraKey = "HHH-14466") + public void testQuery() { + doInHibernate( this::sessionFactory, session -> { + Bar newBar = (Bar) session.createQuery( "SELECT b FROM Bar b WHERE b.foo.id = :id" ) + .setParameter( "id", foo.getId() ) + .uniqueResult(); + assertNotNull( newBar ); + assertNotNull( newBar.getFoo() ); + assertTrue( Hibernate.isInitialized( newBar.getFoo() ) ); + assertEquals( foo.getId(), newBar.getFoo().getId() ); + assertTrue( Hibernate.isInitialized( newBar.getFoo().getBars() ) ); + assertEquals( 1, newBar.getFoo().getBars().size() ); + assertSame( newBar, newBar.getFoo().getBars().iterator().next() ); + assertEquals( "Some details", newBar.getDetails() ); + }); + } + + @Test + @TestForIssue(jiraKey = "HHH-14466") + public void testQueryById() { + + doInHibernate( this::sessionFactory, session -> { + Bar newBar = (Bar) session.createQuery( "SELECT b FROM Bar b WHERE b.foo = :foo" ) + .setParameter( "foo", foo ) + .uniqueResult(); + assertNotNull( newBar ); + assertNotNull( newBar.getFoo() ); + assertTrue( Hibernate.isInitialized( newBar.getFoo() ) ); + assertEquals( foo.getId(), newBar.getFoo().getId() ); + assertTrue( Hibernate.isInitialized( newBar.getFoo().getBars() ) ); + assertEquals( 1, newBar.getFoo().getBars().size() ); + assertSame( newBar, newBar.getFoo().getBars().iterator().next() ); + assertEquals( "Some details", newBar.getDetails() ); + }); + } + + @Test + @TestForIssue(jiraKey = "HHH-14466") + public void testFindByPrimaryKey() { + + doInHibernate( this::sessionFactory, session -> { + Bar newBar = session.find( Bar.class, foo.getId() ); + assertNotNull( newBar ); + assertNotNull( newBar.getFoo() ); + assertTrue( Hibernate.isInitialized( newBar.getFoo() ) ); + assertEquals( foo.getId(), newBar.getFoo().getId() ); + assertTrue( Hibernate.isInitialized( newBar.getFoo().getBars() ) ); + assertEquals( 1, newBar.getFoo().getBars().size() ); + assertSame( newBar, newBar.getFoo().getBars().iterator().next() ); + assertEquals( "Some details", newBar.getDetails() ); + }); + } + + @Test + @TestForIssue(jiraKey = "HHH-14466") + public void testFindByInversePrimaryKey() { + + doInHibernate( this::sessionFactory, session -> { + Foo newFoo = session.find( Foo.class, foo.getId() ); + assertNotNull( newFoo ); + assertNotNull( newFoo.getBars() ); + assertTrue( Hibernate.isInitialized( newFoo.getBars() ) ); + assertEquals( 1, newFoo.getBars().size() ); + assertSame( newFoo, newFoo.getBars().iterator().next().getFoo() ); + assertEquals( "Some details", newFoo.getBars().iterator().next().getDetails() ); + }); + + } + + @Before + public void setupData() { + this.foo = doInHibernate( this::sessionFactory, session -> { + Foo foo = new Foo(); + foo.id = 1L; + session.persist( foo ); + + Bar bar = new Bar(); + bar.setFoo( foo ); + bar.setDetails( "Some details" ); + + foo.getBars().add( bar ); + + session.persist( bar ); + + session.flush(); + + assertNotNull( foo.getId() ); + assertEquals( foo.getId(), bar.getFoo().getId() ); + + return foo; + }); + } + + @After + public void cleanupData() { + doInHibernate( this::sessionFactory, session -> { + session.delete( session.find( Foo.class, foo.id ) ); + }); + this.foo = null; + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Foo.class, + Bar.class, + }; + } + + @Entity(name = "Foo") + public static class Foo { + + @Id + private Long id; + + private String name; + + @OneToMany(mappedBy = "foo", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true) + private Set bars = new HashSet<>(); + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Set getBars() { + return bars; + } + + public void setBars(Set bars) { + this.bars = bars; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + Foo foo = (Foo) o; + return id.equals( foo.id ); + } + + @Override + public int hashCode() { + return Objects.hash( id ); + } + } + + @Entity(name = "Bar") + public static class Bar implements Serializable { + + @Id + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "BAR_ID") + private Foo foo; + + private String details; + + public Foo getFoo() { + return foo; + } + + public void setFoo(Foo foo) { + this.foo = foo; + } + + public String getDetails() { + return details; + } + + public void setDetails(String details) { + this.details = details; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + Bar bar = (Bar) o; + return foo.equals( bar.foo ); + } + + @Override + public int hashCode() { + return Objects.hash( foo ); + } + } +} From cb18fdb4f7ddbe6ed6bf2db7c9399173d6318a9d Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Wed, 24 Feb 2021 17:43:43 -0800 Subject: [PATCH 09/18] HHH-14390 : StackOverflowError with @Fetch(FetchMode.SELECT) mapped for entity with an ID that is a bidirectional one-to-one eager association Move fix into FetchStyleLoadPlanBuildingAssociationVisitationStrategy --- .../org/hibernate/cfg/OneToOneSecondPass.java | 12 ----- ...BuildingAssociationVisitationStrategy.java | 49 +++++++++++++++++++ 2 files changed, 49 insertions(+), 12 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/OneToOneSecondPass.java b/hibernate-core/src/main/java/org/hibernate/cfg/OneToOneSecondPass.java index a8778fb224..7a376ee894 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/OneToOneSecondPass.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/OneToOneSecondPass.java @@ -237,18 +237,6 @@ public class OneToOneSecondPass implements SecondPass { boolean referenceToPrimaryKey = referencesDerivedId || mappedBy == null; value.setReferenceToPrimaryKey( referenceToPrimaryKey ); - // If the other side is an entity with an ID that is derived from - // this side's owner entity, and both sides of the association are eager, - // then this side must be set to FetchMode.SELECT; otherwise, - // there will be an infinite loop attempting to load the derived ID on - // the opposite side. - if ( referencesDerivedId && - !value.isLazy() && - value.getFetchMode() == FetchMode.JOIN && - !otherSideProperty.isLazy() ) { - value.setFetchMode( FetchMode.SELECT ); - } - String propertyRef = value.getReferencedPropertyName(); if ( propertyRef != null ) { buildingContext.getMetadataCollector().addUniquePropertyReference( diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/FetchStyleLoadPlanBuildingAssociationVisitationStrategy.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/FetchStyleLoadPlanBuildingAssociationVisitationStrategy.java index 923cf41d90..0c616e5fed 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/FetchStyleLoadPlanBuildingAssociationVisitationStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/FetchStyleLoadPlanBuildingAssociationVisitationStrategy.java @@ -15,6 +15,7 @@ import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.internal.CoreLogging; import org.hibernate.loader.plan.spi.CollectionReturn; +import org.hibernate.loader.plan.spi.EntityReference; import org.hibernate.loader.plan.spi.EntityReturn; import org.hibernate.loader.plan.spi.FetchSource; import org.hibernate.loader.plan.spi.LoadPlan; @@ -29,6 +30,8 @@ import org.hibernate.persister.walking.spi.WalkingException; import org.hibernate.type.CompositeType; import org.hibernate.type.EmbeddedComponentType; import org.hibernate.type.EntityType; +import org.hibernate.type.ForeignKeyDirection; +import org.hibernate.type.OneToOneType; import org.hibernate.type.Type; import org.jboss.logging.Logger; @@ -252,6 +255,52 @@ public class FetchStyleLoadPlanBuildingAssociationVisitationStrategy } } + if ( attributeType.isEntityType() && + fetchStrategy.getTiming() == FetchTiming.IMMEDIATE && + fetchStrategy.getStyle() == FetchStyle.JOIN ) { + final EntityType entityType = (EntityType) attributeType; + final EntityReference currentEntityReference = currentSource.resolveEntityReference(); + if ( currentEntityReference != null ) { + final EntityPersister currentEntityPersister = currentEntityReference.getEntityPersister(); + if ( entityType.isOneToOne() && entityType.getForeignKeyDirection() == ForeignKeyDirection.TO_PARENT ) { + // attributeDefinition is the "mappedBy" (inverse) side of a + // bidirectional one-to-one association. + final OneToOneType oneToOneType = (OneToOneType) attributeType; + final String associatedUniqueKeyPropertyName = oneToOneType.getIdentifierOrUniqueKeyPropertyName( + sessionFactory() + ); + if ( associatedUniqueKeyPropertyName == null ) { + // The foreign key for the other side of the association is the ID. + // Now check if the ID itself is the other side of the association. + final EntityPersister associatedEntityPersister = (EntityPersister) oneToOneType.getAssociatedJoinable( + sessionFactory() + ); + final Type associatedIdentifierType = associatedEntityPersister.getIdentifierType(); + if ( associatedIdentifierType.isComponentType() && + ( (CompositeType) associatedIdentifierType ).isEmbedded() ) { + final EmbeddedComponentType associatedNonEncapsulatedIdentifierType = + (EmbeddedComponentType) associatedIdentifierType; + if ( associatedNonEncapsulatedIdentifierType.getSubtypes().length == 1 && + EntityType.class.isInstance( associatedNonEncapsulatedIdentifierType.getSubtypes()[0] ) ) { + final EntityType otherSideEntityType = + ( (EntityType) associatedNonEncapsulatedIdentifierType.getSubtypes()[0] ); + if ( otherSideEntityType.isLogicalOneToOne() && + otherSideEntityType.isReferenceToPrimaryKey() && + otherSideEntityType.getAssociatedEntityName().equals( currentEntityPersister.getEntityName() ) ) { + // The associated entity's ID is the other side of the association. + // This side must be set to FetchMode.SELECT; otherwise, + // there will be an infinite loop because the current entity + // would need to be loaded before the associated entity can be loaded, + // but the associated entity cannot be loaded until after the current + // entity is loaded (since the current entity is the associated entity's ID). + return new FetchStrategy( fetchStrategy.getTiming(), FetchStyle.SELECT ); + } + } + } + } + } + } + } return fetchStrategy; } From 34a361058de2f4ae3ae0f6ec8d662537b63d2bfd Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Tue, 2 Mar 2021 14:09:16 -0800 Subject: [PATCH 10/18] HHH-14390 HHH-14466 : StackOverflowError loading inverse side of associations owned by associated entity ID Improved code comments as recommended by Steve Ebersole. --- ...BuildingAssociationVisitationStrategy.java | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/FetchStyleLoadPlanBuildingAssociationVisitationStrategy.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/FetchStyleLoadPlanBuildingAssociationVisitationStrategy.java index 0c616e5fed..0cbe181ef3 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/FetchStyleLoadPlanBuildingAssociationVisitationStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/build/internal/FetchStyleLoadPlanBuildingAssociationVisitationStrategy.java @@ -242,12 +242,11 @@ public class FetchStyleLoadPlanBuildingAssociationVisitationStrategy final EmbeddedComponentType elementIdTypeEmbedded = (EmbeddedComponentType) elementIdType; if ( elementIdTypeEmbedded.getSubtypes().length == 1 && elementIdTypeEmbedded.getPropertyNames()[ 0 ].equals( collectionPersister.getMappedByProperty() ) ) { - // The associated entity's ID is the other (many-to-one) side of the association. - // The one-to-many side must be set to FetchMode.SELECT; otherwise, - // there will be an infinite loop because the current entity - // would need to be loaded before the associated entity can be loaded, - // but the associated entity cannot be loaded until after the current - // entity is loaded (since the current entity is the associated entity's ID). + // The owning side of the inverse collection is defined by the associated entity's id. + // + // Because of how Loaders process ids when processing the ResultSet, this condition would + // lead to an infinite loop. Adjust the fetch to use a SELECT fetch instead of JOIN to + // avoid the infinite loop. return new FetchStrategy( fetchStrategy.getTiming(), FetchStyle.SELECT ); } } @@ -287,12 +286,11 @@ public class FetchStyleLoadPlanBuildingAssociationVisitationStrategy if ( otherSideEntityType.isLogicalOneToOne() && otherSideEntityType.isReferenceToPrimaryKey() && otherSideEntityType.getAssociatedEntityName().equals( currentEntityPersister.getEntityName() ) ) { - // The associated entity's ID is the other side of the association. - // This side must be set to FetchMode.SELECT; otherwise, - // there will be an infinite loop because the current entity - // would need to be loaded before the associated entity can be loaded, - // but the associated entity cannot be loaded until after the current - // entity is loaded (since the current entity is the associated entity's ID). + // The owning side of the inverse to-one is defined by the associated entity's id. + // + // Because of how Loaders process ids when processing the ResultSet, this condition + // would lead to an infinite loop. Adjust the fetch to use a SELECT fetch instead + // of JOIN to avoid the infinite loop. return new FetchStrategy( fetchStrategy.getTiming(), FetchStyle.SELECT ); } } From 44f4f93a294eea75635c3ebfac473db146029291 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Mon, 1 Mar 2021 18:34:09 +0100 Subject: [PATCH 11/18] HHH-9182 Test and fix HQL rules to allow more expression types in aggregate functions --- hibernate-core/src/main/antlr/hql.g | 4 +- .../test/hql/CountExpressionTest.java | 149 ++++++++++++++++++ 2 files changed, 151 insertions(+), 2 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/hql/CountExpressionTest.java diff --git a/hibernate-core/src/main/antlr/hql.g b/hibernate-core/src/main/antlr/hql.g index 321dda67a8..bd05e70aea 100644 --- a/hibernate-core/src/main/antlr/hql.g +++ b/hibernate-core/src/main/antlr/hql.g @@ -842,9 +842,9 @@ castedIdentPrimaryBase ; aggregate - : ( SUM^ | AVG^ | MAX^ | MIN^ ) OPEN! ( additiveExpression | selectStatement ) CLOSE! { #aggregate.setType(AGGREGATE); } + : ( SUM^ | AVG^ | MAX^ | MIN^ ) OPEN! ( concatenation | subQuery ) CLOSE! { #aggregate.setType(AGGREGATE); } // Special case for count - It's 'parameters' can be keywords. - | COUNT^ OPEN! ( STAR { #STAR.setType(ROW_STAR); } | ( ( DISTINCT | ALL )? ( path | collectionExpr | NUM_INT | caseExpression ) ) ) CLOSE! + | COUNT^ OPEN! ( STAR { #STAR.setType(ROW_STAR); } | ( ( DISTINCT | ALL )? ( concatenation | subQuery ) ) ) CLOSE! | collectionExpr ; diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/CountExpressionTest.java b/hibernate-core/src/test/java/org/hibernate/test/hql/CountExpressionTest.java new file mode 100644 index 0000000000..3c6df4c7cb --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/CountExpressionTest.java @@ -0,0 +1,149 @@ +/* + * 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.hql; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.persistence.CollectionTable; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.MapKeyColumn; +import javax.persistence.OneToMany; + +import org.hibernate.dialect.DerbyDialect; +import org.hibernate.dialect.H2Dialect; + +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.SkipForDialect; +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.assertEquals; + +/** + * @author Christian Beikov + */ +public class CountExpressionTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Document.class, + Person.class + }; + } + + @Override + protected void prepareTest() throws Exception { + doInHibernate( this::sessionFactory, session -> { + Document document = new Document(); + document.setId( 1 ); + + Person p1 = new Person(); + Person p2 = new Person(); + + p1.getLocalized().put(1, "p1.1"); + p1.getLocalized().put(2, "p1.2"); + p2.getLocalized().put(1, "p2.1"); + p2.getLocalized().put(2, "p2.2"); + + document.getContacts().put(1, p1); + document.getContacts().put(2, p2); + + session.persist(p1); + session.persist(p2); + session.persist(document); + } ); + } + + @Override + protected boolean isCleanupTestDataRequired() { + return true; + } + + @Test + @TestForIssue(jiraKey = "HHH-9182") + @SkipForDialect(value = DerbyDialect.class, comment = "Derby can't cast from integer to varchar i.e. it requires an intermediary step") + public void testCountDistinctExpression() { + doInHibernate( this::sessionFactory, session -> { + List results = session.createQuery( + "SELECT " + + " d.id, " + + " COUNT(DISTINCT CONCAT(CAST(KEY(l) AS java.lang.String), 'test')) " + + "FROM Document d " + + "LEFT JOIN d.contacts c " + + "LEFT JOIN c.localized l " + + "GROUP BY d.id") + .getResultList(); + + assertEquals(1, results.size()); + Object[] tuple = (Object[]) results.get( 0 ); + assertEquals(1, tuple[0]); + } ); + } + + @Entity(name = "Document") + public static class Document { + + private Integer id; + private Map contacts = new HashMap<>(); + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + @OneToMany + @CollectionTable + @MapKeyColumn(name = "position") + public Map getContacts() { + return contacts; + } + + public void setContacts(Map contacts) { + this.contacts = contacts; + } + } + + + @Entity(name = "Person") + public static class Person { + + private Integer id; + + private Map localized = new HashMap<>(); + + @Id + @GeneratedValue + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + @ElementCollection + public Map getLocalized() { + return localized; + } + + public void setLocalized(Map localized) { + this.localized = localized; + } + } + +} From f7c85fad4ae650d7ec98bf3c71f4c319b1d19c66 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Wed, 3 Mar 2021 10:44:29 +0000 Subject: [PATCH 12/18] HHH-14477 Log warnings about the use of Javassist as BytecodeProvider being deprecated --- .../main/asciidoc/userguide/appendices/Configurations.adoc | 2 +- .../bytecode/internal/javassist/BytecodeProviderImpl.java | 4 ++++ .../src/main/java/org/hibernate/cfg/Environment.java | 1 + .../main/java/org/hibernate/internal/CoreMessageLogger.java | 5 +++++ 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc b/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc index e70b11b420..75ff5d99c9 100644 --- a/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc +++ b/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc @@ -434,7 +434,7 @@ Enable lazy loading feature in runtime bytecode enhancement. This way, even basi Enable association management feature in runtime bytecode enhancement which automatically synchronizes a bidirectional association when only one side is changed. `*hibernate.bytecode.provider*` (e.g. `bytebuddy` (default value)):: -The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/bytecode/spi/BytecodeProvider.html[`BytecodeProvider`] built-in implementation flavor. Currently, only `bytebuddy` and `javassist` are valid values. +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/bytecode/spi/BytecodeProvider.html[`BytecodeProvider`] built-in implementation flavor. Currently, only `bytebuddy` and `javassist` are valid values; `bytebuddy` is the default and recommended choice; `javassist` will be removed soon. `*hibernate.bytecode.use_reflection_optimizer*` (e.g. `true` or `false` (default value)):: Should we use reflection optimization? The reflection optimizer implements the https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/bytecode/spi/ReflectionOptimizer.html[`ReflectionOptimizer`] interface and improves entity instantiation and property getter/setter calls. diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/javassist/BytecodeProviderImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/javassist/BytecodeProviderImpl.java index 995ab83f49..ca588e2519 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/javassist/BytecodeProviderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/javassist/BytecodeProviderImpl.java @@ -21,9 +21,13 @@ import org.jboss.logging.Logger; /** * Bytecode provider implementation for Javassist. + * @deprecated The Javassist based enhancer will be removed soon, + * please use the one based on ByteBuddy (which is the default since + * version 5.3 of Hibernate ORM) * * @author Steve Ebersole */ +@Deprecated public class BytecodeProviderImpl implements BytecodeProvider { private static final CoreMessageLogger LOG = Logger.getMessageLogger( CoreMessageLogger.class, diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/Environment.java b/hibernate-core/src/main/java/org/hibernate/cfg/Environment.java index 824f556d1f..892a07f350 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/Environment.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/Environment.java @@ -346,6 +346,7 @@ public final class Environment implements AvailableSettings { } if ( BYTECODE_PROVIDER_NAME_JAVASSIST.equals( providerName ) ) { + LOG.warnUsingJavassistBytecodeProviderIsDeprecated(); return new org.hibernate.bytecode.internal.javassist.BytecodeProviderImpl(); } 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 096aa3ec3d..04083199e9 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java @@ -1860,4 +1860,9 @@ public interface CoreMessageLogger extends BasicLogger { @Message(value = "Detaching an uninitialized collection with enabled filters from a session: %s", id = 506) void enabledFiltersWhenDetachFromSession(String collectionInfoString); + @LogMessage(level = WARN) + @Message(value = "The Javassist based BytecodeProvider is deprecated. Please switch to using the ByteBuddy based BytecodeProvider, " + + "which is the default since Hibernate ORM 5.3. The Javassist one will be removed soon.", id = 507) + void warnUsingJavassistBytecodeProviderIsDeprecated(); + } From f03dd44107c7c0cf287022d6b02c06690715570f Mon Sep 17 00:00:00 2001 From: johnniang Date: Tue, 2 Mar 2021 18:40:40 +0800 Subject: [PATCH 13/18] HHH-14473 Resolve managed class name with class loader as well --- .../process/internal/ScanningCoordinator.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/ScanningCoordinator.java b/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/ScanningCoordinator.java index 04e34f0bfc..c2179e53fe 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/ScanningCoordinator.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/ScanningCoordinator.java @@ -30,9 +30,9 @@ import org.hibernate.boot.internal.ClassLoaderAccessImpl; import org.hibernate.boot.jaxb.Origin; import org.hibernate.boot.jaxb.SourceType; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; +import org.hibernate.boot.registry.classloading.spi.ClassLoadingException; import org.hibernate.boot.spi.BootstrapContext; import org.hibernate.boot.spi.ClassLoaderAccess; -import org.hibernate.boot.spi.MetadataBuildingOptions; import org.hibernate.boot.spi.XmlMappingBinderAccess; import org.hibernate.cfg.AttributeConverterDefinition; import org.hibernate.service.ServiceRegistry; @@ -112,7 +112,7 @@ public class ScanningCoordinator { return (Scanner) scannerSetting; } - final Class scannerImplClass; + final Class scannerImplClass; if ( Class.class.isInstance( scannerSetting ) ) { scannerImplClass = (Class) scannerSetting; } @@ -270,6 +270,16 @@ public class ScanningCoordinator { continue; } + // Last, try it by loading the class + try { + Class clazz = classLoaderService.classForName( unresolvedListedClassName ); + managedResources.addAnnotatedClassReference( clazz ); + continue; + } + catch (ClassLoadingException ignore) { + // ignore this error + } + log.debugf( "Unable to resolve class [%s] named in persistence unit [%s]", unresolvedListedClassName, From 2d5d6061c50be3d7b0c87281651029a69d0178d3 Mon Sep 17 00:00:00 2001 From: johnniang Date: Wed, 3 Mar 2021 10:58:48 +0800 Subject: [PATCH 14/18] HHH-14473 add test case --- .../internal/ScanningCoordinatorTest.java | 45 +++++++++++++++---- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/boot/model/process/internal/ScanningCoordinatorTest.java b/hibernate-core/src/test/java/org/hibernate/boot/model/process/internal/ScanningCoordinatorTest.java index 4c55e3742b..fe844255ac 100644 --- a/hibernate-core/src/test/java/org/hibernate/boot/model/process/internal/ScanningCoordinatorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/boot/model/process/internal/ScanningCoordinatorTest.java @@ -24,10 +24,10 @@ import org.hibernate.boot.archive.scan.spi.Scanner; import org.hibernate.boot.archive.spi.InputStreamAccess; import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; +import org.hibernate.boot.registry.classloading.spi.ClassLoadingException; import org.hibernate.boot.spi.BootstrapContext; import org.hibernate.boot.spi.XmlMappingBinderAccess; import org.hibernate.internal.CoreMessageLogger; - import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseUnitTestCase; import org.hibernate.testing.logger.LoggerInspectionRule; @@ -41,6 +41,11 @@ import org.jboss.logging.Logger; import org.mockito.Mockito; import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.same; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** @@ -66,10 +71,12 @@ public class ScanningCoordinatorTest extends BaseUnitTestCase { Logger.getMessageLogger( CoreMessageLogger.class, ScanningCoordinator.class.getName() ) ); @Before - public void init(){ + public void init() { + Mockito.reset( managedResources ); Mockito.reset( scanResult ); Mockito.reset( bootstrapContext ); Mockito.reset( scanEnvironment ); + Mockito.reset( classLoaderService ); when( bootstrapContext.getScanEnvironment() ).thenReturn( scanEnvironment ); when( bootstrapContext.getServiceRegistry() ).thenReturn( serviceRegistry ); @@ -78,7 +85,9 @@ public class ScanningCoordinatorTest extends BaseUnitTestCase { when( scanEnvironment.getExplicitlyListedClassNames() ).thenReturn( Arrays.asList( "a.b.C" ) ); - when( classLoaderService.classForName( "a.b.C" ) ).thenReturn( Object.class ); + when( classLoaderService.classForName( eq( "a.b.C" ) ) ).thenThrow( ClassLoadingException.class ); + when( classLoaderService.locateResource( eq( "a/b/c.class" ) ) ).thenReturn( null ); + when( classLoaderService.locateResource( eq( "a/b/c/package-info.class" ) ) ).thenReturn( null ); triggerable = logInspection.watchForLogMessages( "Unable" ); triggerable.reset(); @@ -111,22 +120,40 @@ public class ScanningCoordinatorTest extends BaseUnitTestCase { } @Test - @TestForIssue( jiraKey = "HHH-12505" ) + @TestForIssue(jiraKey = "HHH-14473") + public void testApplyScanResultsToManagedResultsWhileExplicitClassNameLoadable() { + Class expectedClass = Object.class; + when( classLoaderService.classForName( eq( "a.b.C" ) ) ).thenReturn( expectedClass ); + + ScanningCoordinator.INSTANCE.applyScanResultsToManagedResources( + managedResources, + scanResult, + bootstrapContext, + xmlMappingBinderAccess + ); + + verify( managedResources, times( 0 ) ).addAnnotatedClassName( any() ); + verify( managedResources, times( 1 ) ).addAnnotatedClassReference( same( expectedClass ) ); + verify( classLoaderService, times( 1 ) ).classForName( eq( "a.b.C" ) ); + } + + @Test + @TestForIssue(jiraKey = "HHH-12505") public void testManagedResourcesAfterCoordinateScanWithDisabledScanner() { assertManagedResourcesAfterCoordinateScanWithScanner( new DisabledScanner(), true ); } @Test - @TestForIssue( jiraKey = "HHH-12505" ) + @TestForIssue(jiraKey = "HHH-12505") public void testManagedResourcesAfterCoordinateScanWithCustomEnabledScanner() { final Scanner scanner = new Scanner() { @Override public ScanResult scan(final ScanEnvironment environment, final ScanOptions options, final ScanParameters parameters) { final InputStreamAccess dummyInputStreamAccess = new ByteArrayInputStreamAccess( "dummy", new byte[0] ); return new ScanResultImpl( - Collections.singleton( new PackageDescriptorImpl( "dummy", dummyInputStreamAccess ) ), - Collections.singleton( new ClassDescriptorImpl( "dummy", ClassDescriptor.Categorization.MODEL, dummyInputStreamAccess ) ), - Collections.singleton( new MappingFileDescriptorImpl( "dummy", dummyInputStreamAccess ) ) + Collections.singleton( new PackageDescriptorImpl( "dummy", dummyInputStreamAccess ) ), + Collections.singleton( new ClassDescriptorImpl( "dummy", ClassDescriptor.Categorization.MODEL, dummyInputStreamAccess ) ), + Collections.singleton( new MappingFileDescriptorImpl( "dummy", dummyInputStreamAccess ) ) ); } }; @@ -184,7 +211,7 @@ public class ScanningCoordinatorTest extends BaseUnitTestCase { ScanningCoordinator.INSTANCE.coordinateScan( managedResources, bootstrapContext, xmlMappingBinderAccess ); assertEquals( 1, scanEnvironment.getExplicitlyListedClassNames().size() ); - assertEquals( "a.b.C", scanEnvironment.getExplicitlyListedClassNames().get(0) ); + assertEquals( "a.b.C", scanEnvironment.getExplicitlyListedClassNames().get( 0 ) ); assertEquals( true, managedResources.getAttributeConverterDefinitions().isEmpty() ); assertEquals( true, managedResources.getAnnotatedClassReferences().isEmpty() ); From ed3bbf15e476b064b6a55a80ac0c1570a4287bf0 Mon Sep 17 00:00:00 2001 From: Vlad Mihalcea Date: Mon, 12 Nov 2018 13:35:21 +0200 Subject: [PATCH 15/18] HHH-13077 - Optimize query plan call count --- .../AbstractSharedSessionContract.java | 21 +-- .../org/hibernate/internal/SessionImpl.java | 2 +- .../main/java/org/hibernate/query/Query.java | 2 +- .../query/internal/AbstractProducedQuery.java | 19 +- .../hibernate/query/internal/QueryImpl.java | 41 ++++- .../QueryPlanCacheStatisticsTest.java | 173 +++++++++++++++++- 6 files changed, 233 insertions(+), 25 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 00b5b698af..6b7a927765 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java @@ -647,11 +647,11 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont throw getExceptionConverter().convert( new IllegalArgumentException( "No query defined for that name [" + name + "]" ) ); } - protected QueryImplementor createQuery(NamedQueryDefinition queryDefinition) { + protected QueryImpl createQuery(NamedQueryDefinition queryDefinition) { String queryString = queryDefinition.getQueryString(); final QueryImpl query = new QueryImpl( this, - getQueryPlan( queryString, false ).getParameterMetadata(), + getQueryPlan( queryString, false ), queryString ); applyQuerySettingsAndHints( query ); @@ -723,7 +723,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont } @Override - public QueryImplementor createQuery(String queryString) { + public QueryImpl createQuery(String queryString) { checkOpen(); pulseTransactionCoordinator(); delayedAfterCompletion(); @@ -731,7 +731,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont try { final QueryImpl query = new QueryImpl( this, - getQueryPlan( queryString, false ).getParameterMetadata(), + getQueryPlan( queryString, false ), queryString ); applyQuerySettingsAndHints( query ); @@ -831,7 +831,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont try { // do the translation - final QueryImplementor query = createQuery( queryString ); + final QueryImpl query = createQuery( queryString ); resultClassChecking( resultClass, query ); return query; } @@ -841,13 +841,10 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont } @SuppressWarnings({"unchecked", "WeakerAccess", "StatementWithEmptyBody"}) - protected void resultClassChecking(Class resultClass, org.hibernate.Query hqlQuery) { + protected void resultClassChecking(Class resultClass, QueryImpl hqlQuery) { // make sure the query is a select -> HHH-7192 - final HQLQueryPlan queryPlan = getFactory().getQueryPlanCache().getHQLQueryPlan( - hqlQuery.getQueryString(), - false, - getLoadQueryInfluencers().getEnabledFilters() - ); + HQLQueryPlan queryPlan = hqlQuery.getQueryPlan(); + if ( queryPlan.getTranslators()[0].isManipulationStatement() ) { throw new IllegalArgumentException( "Update/delete queries cannot be typed" ); } @@ -923,7 +920,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont @SuppressWarnings({"WeakerAccess", "unchecked"}) protected QueryImplementor createQuery(NamedQueryDefinition namedQueryDefinition, Class resultType) { - final QueryImplementor query = createQuery( namedQueryDefinition ); + final QueryImpl query = createQuery( namedQueryDefinition ); if ( resultType != null ) { resultClassChecking( resultType, query ); } 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 a2af475246..6cef2427fa 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -1528,7 +1528,7 @@ public class SessionImpl queryParameters.validateParameters(); HQLQueryPlan plan = queryParameters.getQueryPlan(); - if ( plan == null ) { + if ( plan == null || !plan.isShallow() ) { plan = getQueryPlan( query, true ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/Query.java b/hibernate-core/src/main/java/org/hibernate/query/Query.java index c56e1ea1a0..9dfc5f77a8 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/Query.java +++ b/hibernate-core/src/main/java/org/hibernate/query/Query.java @@ -67,7 +67,7 @@ import org.hibernate.type.Type; * @author Steve Ebersole * @author Gavin King */ -@SuppressWarnings("UnusedDeclaration") +@SuppressWarnings("UnusedDeclaratiqon") public interface Query extends TypedQuery, org.hibernate.Query, CommonQueryContract { /** * Get the QueryProducer this Query originates from. diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/AbstractProducedQuery.java b/hibernate-core/src/main/java/org/hibernate/query/internal/AbstractProducedQuery.java index 87a28dfbea..1ca1739101 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/AbstractProducedQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/AbstractProducedQuery.java @@ -1434,8 +1434,8 @@ public abstract class AbstractProducedQuery implements QueryImplementor { ); } - QueryParameters queryParameters = new QueryParameters( - getQueryParameterBindings(), + QueryParameters queryParameters = new QueryParameters( + getQueryParameterBindings(), getLockOptions(), queryOptions, true, @@ -1450,13 +1450,24 @@ public abstract class AbstractProducedQuery implements QueryImplementor { optionalId, resultTransformer ); - queryParameters.setQueryPlan( entityGraphHintedQueryPlan ); + + appendQueryPlanToQueryParameters( hql, queryParameters, entityGraphHintedQueryPlan ); + if ( passDistinctThrough != null ) { queryParameters.setPassDistinctThrough( passDistinctThrough ); } return queryParameters; } + protected void appendQueryPlanToQueryParameters( + String hql, + QueryParameters queryParameters, + HQLQueryPlan queryPlan) { + if ( queryPlan != null ) { + queryParameters.setQueryPlan( queryPlan ); + } + } + public QueryParameters getQueryParameters() { final String expandedQuery = getQueryParameterBindings().expandListValuedParameters( getQueryString(), getProducer() ); return makeQueryParametersForExecution( expandedQuery ); @@ -1732,7 +1743,7 @@ public abstract class AbstractProducedQuery implements QueryImplementor { : defaultType; } - private boolean isSelect() { + protected boolean isSelect() { return getProducer().getFactory().getQueryPlanCache() .getHQLQueryPlan( getQueryString(), false, Collections.emptyMap() ) .isSelect(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryImpl.java b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryImpl.java index cda7388a8c..35d8ad55dd 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryImpl.java @@ -6,8 +6,10 @@ */ package org.hibernate.query.internal; +import org.hibernate.engine.query.spi.HQLQueryPlan; +import org.hibernate.engine.query.spi.ReturnMetadata; +import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.query.ParameterMetadata; import org.hibernate.query.Query; import org.hibernate.query.spi.QueryParameterBindings; import org.hibernate.type.Type; @@ -18,16 +20,19 @@ import org.hibernate.type.Type; public class QueryImpl extends AbstractProducedQuery implements Query { private final String queryString; + private final HQLQueryPlan hqlQueryPlan; + private final QueryParameterBindingsImpl queryParameterBindings; public QueryImpl( SharedSessionContractImplementor producer, - ParameterMetadata parameterMetadata, + HQLQueryPlan hqlQueryPlan, String queryString) { - super( producer, parameterMetadata ); + super( producer, hqlQueryPlan.getParameterMetadata() ); + this.hqlQueryPlan = hqlQueryPlan; this.queryString = queryString; this.queryParameterBindings = QueryParameterBindingsImpl.from( - parameterMetadata, + hqlQueryPlan.getParameterMetadata(), producer.getFactory(), producer.isQueryParametersValidationEnabled() ); @@ -43,6 +48,10 @@ public class QueryImpl extends AbstractProducedQuery implements Query { return queryString; } + public HQLQueryPlan getQueryPlan() { + return hqlQueryPlan; + } + @Override protected boolean isNativeQuery() { return false; @@ -50,12 +59,14 @@ public class QueryImpl extends AbstractProducedQuery implements Query { @Override public Type[] getReturnTypes() { - return getProducer().getFactory().getReturnTypes( queryString ); + final ReturnMetadata metadata = hqlQueryPlan.getReturnMetadata(); + return metadata == null ? null : metadata.getReturnTypes(); } @Override public String[] getReturnAliases() { - return getProducer().getFactory().getReturnAliases( queryString ); + final ReturnMetadata metadata = hqlQueryPlan.getReturnMetadata(); + return metadata == null ? null : metadata.getReturnAliases(); } @Override @@ -67,4 +78,22 @@ public class QueryImpl extends AbstractProducedQuery implements Query { public Query setEntity(String name, Object val) { return setParameter( name, val, getProducer().getFactory().getTypeHelper().entity( resolveEntityName( val ) ) ); } + + @Override + protected boolean isSelect() { + return hqlQueryPlan.isSelect(); + } + + @Override + protected void appendQueryPlanToQueryParameters( + String hql, + QueryParameters queryParameters, + HQLQueryPlan queryPlan) { + if ( queryPlan != null ) { + queryParameters.setQueryPlan( queryPlan ); + } + else if ( hql.equals( getQueryString() ) ) { + queryParameters.setQueryPlan( getQueryPlan() ); + } + } } diff --git a/hibernate-core/src/test/java/org/hibernate/stat/internal/QueryPlanCacheStatisticsTest.java b/hibernate-core/src/test/java/org/hibernate/stat/internal/QueryPlanCacheStatisticsTest.java index a0a02b1705..5974b976b0 100644 --- a/hibernate-core/src/test/java/org/hibernate/stat/internal/QueryPlanCacheStatisticsTest.java +++ b/hibernate-core/src/test/java/org/hibernate/stat/internal/QueryPlanCacheStatisticsTest.java @@ -6,22 +6,28 @@ */ package org.hibernate.stat.internal; +import java.util.List; import java.util.Map; + import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; +import javax.persistence.LockModeType; +import javax.persistence.NamedQuery; +import javax.persistence.Tuple; +import javax.persistence.TypedQuery; import org.hibernate.SessionFactory; import org.hibernate.cfg.Environment; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.stat.QueryStatistics; import org.hibernate.stat.Statistics; - import org.hibernate.testing.TestForIssue; import org.junit.Test; import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; /** @@ -39,6 +45,7 @@ public class QueryPlanCacheStatisticsTest extends BaseEntityManagerFunctionalTes }; } + @Override protected void addConfigOptions(Map options) { options.put( Environment.GENERATE_STATISTICS, "true" ); } @@ -63,6 +70,7 @@ public class QueryPlanCacheStatisticsTest extends BaseEntityManagerFunctionalTes @Test public void test() { + statistics.clear(); assertEquals( 0, statistics.getQueryPlanCacheHitCount() ); assertEquals( 0, statistics.getQueryPlanCacheMissCount() ); @@ -115,6 +123,165 @@ public class QueryPlanCacheStatisticsTest extends BaseEntityManagerFunctionalTes } ); } + @Test + @TestForIssue( jiraKey = "HHH-13077" ) + public void testCreateQueryHitCount() { + statistics.clear(); + + doInJPA( this::entityManagerFactory, entityManager -> { + + List employees = entityManager.createQuery( + "select e from Employee e", Employee.class ) + .getResultList(); + + assertEquals( 5, employees.size() ); + + //First time, we get a cache miss, so the query is compiled + assertEquals( 1, statistics.getQueryPlanCacheMissCount() ); + //The hit count should be 0 as we don't need to go to the cache after we already compiled the query + assertEquals( 0, statistics.getQueryPlanCacheHitCount() ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + + List employees = entityManager.createQuery( + "select e from Employee e", Employee.class ) + .getResultList(); + + assertEquals( 5, employees.size() ); + + //The miss count is still 1, as no we got the query plan from the cache + assertEquals( 1, statistics.getQueryPlanCacheMissCount() ); + //And the cache hit count increases. + assertEquals( 1, statistics.getQueryPlanCacheHitCount() ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + + List employees = entityManager.createQuery( + "select e from Employee e", Employee.class ) + .getResultList(); + + assertEquals( 5, employees.size() ); + + //The miss count is still 1, as no we got the query plan from the cache + assertEquals( 1, statistics.getQueryPlanCacheMissCount() ); + //And the cache hit count increases. + assertEquals( 2, statistics.getQueryPlanCacheHitCount() ); + } ); + } + + @Test + @TestForIssue( jiraKey = "HHH-13077" ) + public void testCreateNamedQueryHitCount() { + //This is for the NamedQuery that gets compiled + assertEquals( 1, statistics.getQueryPlanCacheMissCount() ); + statistics.clear(); + + doInJPA( this::entityManagerFactory, entityManager -> { + + Employee employees = entityManager.createNamedQuery( + "find_employee_by_name", Employee.class ) + .setParameter( "name", "Employee: 1" ) + .getSingleResult(); + + //The miss count is 0 because the plan was compiled when the EMF was built, and we cleared the Statistics + assertEquals( 0, statistics.getQueryPlanCacheMissCount() ); + //The hit count is 1 since we got the plan from the cache + assertEquals( 1, statistics.getQueryPlanCacheHitCount() ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + + Employee employees = entityManager.createNamedQuery( + "find_employee_by_name", Employee.class ) + .setParameter( "name", "Employee: 1" ) + .getSingleResult(); + + //The miss count is still 0 because the plan was compiled when the EMF was built, and we cleared the Statistics + assertEquals( 0, statistics.getQueryPlanCacheMissCount() ); + //The hit count is 2 since we got the plan from the cache twice + assertEquals( 2, statistics.getQueryPlanCacheHitCount() ); + } ); + } + + @Test + @TestForIssue( jiraKey = "HHH-13077" ) + public void testCreateQueryTupleHitCount() { + statistics.clear(); + + doInJPA( this::entityManagerFactory, entityManager -> { + + List employees = entityManager.createQuery( + "select e.id, e.name from Employee e", Tuple.class ) + .getResultList(); + + assertEquals( 5, employees.size() ); + + //First time, we get a cache miss, so the query is compiled + assertEquals( 1, statistics.getQueryPlanCacheMissCount() ); + //The hit count should be 0 as we don't need to go to the cache after we already compiled the query + assertEquals( 0, statistics.getQueryPlanCacheHitCount() ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + + List employees = entityManager.createQuery( + "select e.id, e.name from Employee e", Tuple.class ) + .getResultList(); + + assertEquals( 5, employees.size() ); + + //The miss count is still 1, as no we got the query plan from the cache + assertEquals( 1, statistics.getQueryPlanCacheMissCount() ); + //And the cache hit count increases. + assertEquals( 1, statistics.getQueryPlanCacheHitCount() ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + + List employees = entityManager.createQuery( + "select e.id, e.name from Employee e", Tuple.class ) + .getResultList(); + + assertEquals( 5, employees.size() ); + + //The miss count is still 1, as no we got the query plan from the cache + assertEquals( 1, statistics.getQueryPlanCacheMissCount() ); + //And the cache hit count increases. + assertEquals( 2, statistics.getQueryPlanCacheHitCount() ); + } ); + } + + @Test + @TestForIssue(jiraKey = "HHH-13077") + public void testLockModeHitCount() { + statistics.clear(); + + doInJPA( this::entityManagerFactory, entityManager -> { + TypedQuery typedQuery = entityManager.createQuery( "select e from Employee e", Employee.class ); + + List employees = typedQuery.getResultList(); + + assertEquals( 5, employees.size() ); + + //First time, we get a cache miss, so the query is compiled + assertEquals( 1, statistics.getQueryPlanCacheMissCount() ); + //The hit count should be 0 as we don't need to go to the cache after we already compiled the query + assertEquals( 0, statistics.getQueryPlanCacheHitCount() ); + + typedQuery.setLockMode( LockModeType.READ ); + + //The hit count should still be 0 as setLockMode() shouldn't trigger a cache hit + assertEquals( 0, statistics.getQueryPlanCacheHitCount() ); + + assertNotNull( typedQuery.getLockMode() ); + + //The hit count should still be 0 as getLockMode() shouldn't trigger a cache hit + assertEquals( 0, statistics.getQueryPlanCacheHitCount() ); + } ); + } + private void assertQueryStatistics(String hql, int hitCount) { QueryStatistics queryStatistics = statistics.getQueryStatistics( hql ); @@ -125,6 +292,10 @@ public class QueryPlanCacheStatisticsTest extends BaseEntityManagerFunctionalTes } @Entity(name = "Employee") + @NamedQuery( + name = "find_employee_by_name", + query = "select e from Employee e where e.name = :name" + ) public static class Employee { @Id From 81071a4594d4347279222632bdc76a1ded44bebe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Wed, 24 Feb 2021 15:24:14 +0100 Subject: [PATCH 16/18] HHH-14439 Clean up expanded list parameters before re-executing a query MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Yoann Rodière --- .../internal/QueryParameterBindingsImpl.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingsImpl.java b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingsImpl.java index d5122e03ae..7759c14520 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingsImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingsImpl.java @@ -63,6 +63,7 @@ public class QueryParameterBindingsImpl implements QueryParameterBindings { private Map parameterBindingMap; private Map parameterListBindingMap; private Set parametersConvertedToListBindings; + private Set syntheticParametersFromListBindings; public static QueryParameterBindingsImpl from( ParameterMetadata parameterMetadata, @@ -514,6 +515,12 @@ public class QueryParameterBindingsImpl implements QueryParameterBindings { return null; } + if ( syntheticParametersFromListBindings != null ) { + // Clean up parameters from previous query executions + parameterBindingMap.keySet().removeAll( syntheticParametersFromListBindings ); + syntheticParametersFromListBindings.clear(); + } + if ( parameterListBindingMap == null || parameterListBindingMap.isEmpty() ) { return queryString; } @@ -644,6 +651,7 @@ public class QueryParameterBindingsImpl implements QueryParameterBindings { expansionList.append( "?" ).append( syntheticParam.getPosition() ); } + registerSyntheticParamFromListBindings( syntheticParam ); final QueryParameterBinding syntheticBinding = makeBinding( entry.getValue().getBindType() ); syntheticBinding.setBindValue( bindValue ); parameterBindingMap.put( syntheticParam, syntheticBinding ); @@ -669,6 +677,13 @@ public class QueryParameterBindingsImpl implements QueryParameterBindings { return queryString; } + private void registerSyntheticParamFromListBindings(QueryParameter syntheticParam) { + if ( syntheticParametersFromListBindings == null ) { + syntheticParametersFromListBindings = new HashSet<>(); + } + syntheticParametersFromListBindings.add( syntheticParam ); + } + private int getMaxOrdinalPosition() { int maxOrdinalPosition = 0; for ( QueryParameter queryParameter : parameterBindingMap.keySet() ) { From fb079d077cf34e206084d796d7c53e5e606a6436 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Wed, 24 Feb 2021 14:18:26 +0100 Subject: [PATCH 17/18] HHH-14439 Test executing the same query with subselects a second time with different list parameters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Yoann Rodière --- ...yListParametersWithFetchSubSelectTest.java | 215 ++++++++++++++++++ 1 file changed, 215 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/annotations/query/QueryListParametersWithFetchSubSelectTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/query/QueryListParametersWithFetchSubSelectTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/query/QueryListParametersWithFetchSubSelectTest.java new file mode 100644 index 0000000000..fe6402ba94 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/query/QueryListParametersWithFetchSubSelectTest.java @@ -0,0 +1,215 @@ +/* + * 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.annotations.query; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.TypedQuery; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.Fetch; +import org.hibernate.annotations.FetchMode; +import org.hibernate.cfg.Configuration; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.jdbc.SQLStatementInterceptor; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests the handling and expansion of list parameters, + * particularly when using {@code @Fetch(FetchMode.SUBSELECT)} + * (because this fetch mode involves building a map of parameters). + */ +public class QueryListParametersWithFetchSubSelectTest extends BaseCoreFunctionalTestCase { + + private SQLStatementInterceptor sqlStatementInterceptor; + + @Override + protected void configure(Configuration configuration) { + sqlStatementInterceptor = new SQLStatementInterceptor( configuration ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Parent.class, Child.class }; + } + + @Override + protected void afterSessionFactoryBuilt() { + inTransaction( s -> { + for ( int i = 0; i < 10; i++ ) { + Parent parent = new Parent( i ); + s.persist( parent ); + for ( int j = 0; j < 10; j++ ) { + Child child = new Child( i * 100 + j, parent ); + parent.children.add( child ); + s.persist( child ); + } + } + } ); + } + + @Test + public void simple() { + sqlStatementInterceptor.clear(); + + inTransaction( s -> { + TypedQuery query = s.createQuery( "select p from Parent p where id in :ids", Parent.class ); + query.setParameter( "ids", Arrays.asList( 0, 1, 2 ) ); + List results = query.getResultList(); + assertThat( results ) + .allSatisfy( parent -> assertThat( Hibernate.isInitialized( parent.getChildren() ) ).isTrue() ) + .extracting( Parent::getId ).containsExactly( 0, 1, 2 ); + } ); + + // If we get here, children were initialized eagerly. + // Did ORM actually use subselects? + assertThat( sqlStatementInterceptor.getSqlQueries() ).hasSize( 2 ); + } + + @Test + @TestForIssue(jiraKey = "HHH-14439") + public void reusingQueryWithFewerNamedParameters() { + sqlStatementInterceptor.clear(); + + inTransaction( s -> { + TypedQuery query = s.createQuery( "select p from Parent p where id in :ids", Parent.class ); + + query.setParameter( "ids", Arrays.asList( 0, 1, 2, 3 ) ); + List results = query.getResultList(); + assertThat( results ) + .allSatisfy( parent -> assertThat( Hibernate.isInitialized( parent.getChildren() ) ).isTrue() ) + .extracting( Parent::getId ).containsExactly( 0, 1, 2, 3 ); + + query.setParameter( "ids", Arrays.asList( 4, 5, 6 ) ); + results = query.getResultList(); + assertThat( results ) + .allSatisfy( parent -> assertThat( Hibernate.isInitialized( parent.getChildren() ) ).isTrue() ) + .extracting( Parent::getId ).containsExactly( 4, 5, 6 ); + + query.setParameter( "ids", Arrays.asList( 7, 8 ) ); + results = query.getResultList(); + assertThat( results ) + .allSatisfy( parent -> assertThat( Hibernate.isInitialized( parent.getChildren() ) ).isTrue() ) + .extracting( Parent::getId ).containsExactly( 7, 8 ); + } ); + + // If we get here, children were initialized eagerly. + // Did ORM actually use subselects? + assertThat( sqlStatementInterceptor.getSqlQueries() ).hasSize( 3 * 2 ); + } + + + @Test + @TestForIssue(jiraKey = "HHH-14439") + public void reusingQueryWithFewerOrdinalParameters() { + sqlStatementInterceptor.clear(); + + inTransaction( s -> { + TypedQuery query = s.createQuery( "select p from Parent p where id in ?0", Parent.class ); + + query.setParameter( 0, Arrays.asList( 0, 1, 2, 3 ) ); + List results = query.getResultList(); + assertThat( results ) + .allSatisfy( parent -> assertThat( Hibernate.isInitialized( parent.getChildren() ) ).isTrue() ) + .extracting( Parent::getId ).containsExactly( 0, 1, 2, 3 ); + + query.setParameter( 0, Arrays.asList( 4, 5, 6 ) ); + results = query.getResultList(); + assertThat( results ) + .allSatisfy( parent -> assertThat( Hibernate.isInitialized( parent.getChildren() ) ).isTrue() ) + .extracting( Parent::getId ).containsExactly( 4, 5, 6 ); + + query.setParameter( 0, Arrays.asList( 7, 8 ) ); + results = query.getResultList(); + assertThat( results ) + .allSatisfy( parent -> assertThat( Hibernate.isInitialized( parent.getChildren() ) ).isTrue() ) + .extracting( Parent::getId ).containsExactly( 7, 8 ); + } ); + + // If we get here, children were initialized eagerly. + // Did ORM actually use subselects? + assertThat( sqlStatementInterceptor.getSqlQueries() ).hasSize( 3 * 2 ); + } + + @Entity(name = "Parent") + public static class Parent { + @Id + private Integer id; + + @OneToMany(mappedBy = "parent", fetch = FetchType.EAGER) + @Fetch(FetchMode.SUBSELECT) + private List children = new ArrayList<>(); + + public Parent() { + } + + public Parent(Integer id) { + this.id = id; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } + } + + @Entity(name = "Child") + public static class Child { + + @Id + private Integer id; + + @ManyToOne + private Parent parent; + + public Child() { + } + + public Child(Integer id, Parent parent) { + this.id = id; + this.parent = parent; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Parent getParent() { + return parent; + } + + public void setParent(Parent parent) { + this.parent = parent; + } + } + +} From 17c5fab50e9a1c90a96a01498b8c95b1646194cc Mon Sep 17 00:00:00 2001 From: Vlad Mihalcea Date: Fri, 21 Feb 2020 10:11:12 +0200 Subject: [PATCH 18/18] HHH-12338 - Incorrect metamodel for basic collections --- .../MetaAttributeGenerationVisitor.java | 17 ++++ .../CollectionAsBasicTypeTest.java | 89 +++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 tooling/metamodel-generator/src/test/java/org/hibernate/jpamodelgen/test/collectionbasictype/CollectionAsBasicTypeTest.java diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/MetaAttributeGenerationVisitor.java b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/MetaAttributeGenerationVisitor.java index 5a1117b850..58226681d7 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/MetaAttributeGenerationVisitor.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/MetaAttributeGenerationVisitor.java @@ -138,6 +138,23 @@ public class MetaAttributeGenerationVisitor extends SimpleTypeVisitor6. + */ +package org.hibernate.jpamodelgen.test.collectionbasictype; + +import org.hibernate.jpamodelgen.test.util.CompilationTest; +import org.hibernate.jpamodelgen.test.util.TestForIssue; +import org.hibernate.jpamodelgen.test.util.WithClasses; + +import org.junit.Test; + +import static org.hibernate.jpamodelgen.test.util.TestUtil.assertAttributeTypeInMetaModelFor; +import static org.hibernate.jpamodelgen.test.util.TestUtil.assertListAttributeTypeInMetaModelFor; +import static org.hibernate.jpamodelgen.test.util.TestUtil.assertMetamodelClassGeneratedFor; + +/** + * @author helloztt + */ +public class CollectionAsBasicTypeTest extends CompilationTest { + + @Test + @TestForIssue(jiraKey = "HHH-12338") + @WithClasses({Goods.class, Product.class}) + public void testConvert() throws ClassNotFoundException, NoSuchFieldException { + assertMetamodelClassGeneratedFor(Product.class); + assertMetamodelClassGeneratedFor(Goods.class); + assertListAttributeTypeInMetaModelFor( + Goods.class, + "productList", + Product.class, + "ListAttribute generic type should be Product" + ); + assertAttributeTypeInMetaModelFor( + Goods.class, + "tags", + Goods.class.getDeclaredField("tags").getGenericType(), + "Wrong meta model type" + ); + + } + + @Test + @TestForIssue(jiraKey = "HHH-12338") + @WithClasses({Person.class}) + public void testListType() throws ClassNotFoundException, NoSuchFieldException { + assertMetamodelClassGeneratedFor(Person.class); + + assertAttributeTypeInMetaModelFor( + Person.class, + "phones", + Person.class.getDeclaredField("phones").getGenericType(), + "Wrong meta model type" + ); + + } + + @Test + @TestForIssue(jiraKey = "HHH-12338") + @WithClasses({PersonPhone.class}) + public void testListTypeWithImport() throws ClassNotFoundException, NoSuchFieldException { + assertMetamodelClassGeneratedFor(PersonPhone.class); + + assertAttributeTypeInMetaModelFor( + PersonPhone.class, + "phones", + PersonPhone.class.getDeclaredField("phones").getGenericType(), + "Wrong meta model type" + ); + + } + + @Test + @TestForIssue(jiraKey = "HHH-12338") + @WithClasses({PhoneBook.class}) + public void testMapType() throws ClassNotFoundException, NoSuchFieldException { + assertMetamodelClassGeneratedFor(PhoneBook.class); + + assertAttributeTypeInMetaModelFor( + PhoneBook.class, + "phones", + PhoneBook.class.getDeclaredField("phones").getGenericType(), + "Wrong meta model type" + ); + + } +}