diff --git a/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc b/documentation/src/main/asciidoc/userguide/appendices/Configurations.adoc index d5e6194200..61f93d0bd9 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/boot/model/process/internal/ScanningCoordinator.java b/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/ScanningCoordinator.java index 8aaa551f12..f7d0f76c29 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,6 +30,7 @@ import org.hibernate.boot.jaxb.Origin; import org.hibernate.boot.jaxb.SourceType; import org.hibernate.boot.model.convert.internal.ClassBasedConverterDescriptor; 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.XmlMappingBinderAccess; @@ -110,7 +111,7 @@ public class ScanningCoordinator { return (Scanner) scannerSetting; } - final Class scannerImplClass; + final Class scannerImplClass; if ( Class.class.isInstance( scannerSetting ) ) { scannerImplClass = (Class) scannerSetting; } @@ -269,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 + } + if ( log.isDebugEnabled() ) { log.debugf( "Unable to resolve class [%s] named in persistence unit [%s]", 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 f51e89ff86..6883b594e8 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/OneToOneSecondPass.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/OneToOneSecondPass.java @@ -238,18 +238,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/collection/internal/AbstractPersistentCollection.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java index c739c7bbbe..97eac6b2fb 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 @@ -642,6 +642,10 @@ public abstract class AbstractPersistentCollection implements Serializable, P 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/AbstractSharedSessionContract.java b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java index 27a4951f12..5af009c9cc 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java @@ -185,7 +185,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont this.transactionCoordinator = sharedOptions.getTransactionCoordinator(); this.jdbcCoordinator = sharedOptions.getJdbcCoordinator(); - // todo : "wrap" the transaction to no-op comit/rollback attempts? + // todo : "wrap" the transaction to no-op commit/rollback attempts? this.currentHibernateTransaction = sharedOptions.getTransaction(); if ( sharedOptions.shouldAutoJoinTransactions() ) { @@ -249,7 +249,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont private StatementInspector interpret(StatementInspector statementInspector) { if ( statementInspector == null ) { // If there is no StatementInspector specified, map to the call - // to the (deprecated) Interceptor #onPrepareStatement method + // to the (deprecated) Interceptor#onPrepareStatement method return (StatementInspector) interceptor::onPrepareStatement; } return statementInspector; @@ -292,7 +292,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont @Override public UUID getSessionIdentifier() { if ( this.sessionIdentifier == null ) { - //Lazily initialized: otherwise all the UUID generations will cause of significant amount of contention. + //Lazily initialized: otherwise all the UUID generations will cause significant amount of contention. this.sessionIdentifier = StandardRandomStrategy.INSTANCE.generateUUID( null ); } return sessionIdentifier; @@ -725,7 +725,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont if ( resultSetMappingMemento == null ) { throw new HibernateException( "Could not resolve specified result-set mapping name : " + resultSetMappingName ); } - + query = new NativeQueryImpl( sqlString, resultSetMappingMemento, this ); } else { 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 f99455af87..225e850a52 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java @@ -1828,4 +1828,13 @@ 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); + + @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(); + } 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 6c24dc0fa7..6808f82de5 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 @@ -1332,30 +1332,48 @@ public abstract class AbstractProducedQuery implements QueryImplementor { // entityGraphHintedQueryPlan = null; // } // else { +// final SharedSessionContractImplementor producer = getProducer(); // entityGraphHintedQueryPlan = new HQLQueryPlan( // hql, // false, -// getSession().getLoadQueryInfluencers().getEnabledFilters(), -// getSession().getFactory(), +// producer.getLoadQueryInfluencers().getEnabledFilters(), +// producer.getFactory(), // entityGraphQueryHint // ); // } // -// final QueryParameters queryParameters = new QueryParameters( +// QueryParameters queryParameters = new QueryParameters( // getQueryParameterBindings(), // getLockOptions(), // queryOptions, +// true, +// isReadOnly(), +// cacheable, +// cacheRegion, +// comment, +// dbHints, // null, // optionalObject, // optionalEntityName, // 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 ); +// } // } private FlushMode sessionFlushMode; @@ -1551,7 +1569,7 @@ public abstract class AbstractProducedQuery implements QueryImplementor { this.optionalObject = optionalObject; } - private boolean isSelect() { + protected boolean isSelect() { throw new NotYetImplementedFor6Exception( getClass() ); // return getSession().getFactory().getQueryInterpretationCache() 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 66a1b928f6..54792b6175 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; @@ -517,5 +518,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; + } + } } 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 3af7db3342..066efd7792 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.spi.InputStreamAccess; import org.hibernate.boot.model.convert.spi.ConverterDescriptor; 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.getAttributeConverterDescriptors().isEmpty() ); assertEquals( true, managedResources.getAnnotatedClassReferences().isEmpty() ); 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 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 ); + } + } +} 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; + } + } + +} 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; + } + } + +} 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" + ); + + } +}