From 9084ce497ee45afec1451f8b0988942cf7976847 Mon Sep 17 00:00:00 2001 From: Vlad Mihalcea Date: Fri, 7 Feb 2020 16:57:50 +0200 Subject: [PATCH 01/27] HHH-13853 - Pass the merged Integration settings and Persistence Unit properties to buildBootstrapServiceRegistry --- .../EntityManagerFactoryBuilderImpl.java | 13 ++++- .../BaseEntityManagerFunctionalTestCase.java | 2 +- ...iderSettingByClassUsingPropertiesTest.java | 57 +++++++++++++++++++ .../schemagen/JpaSchemaGeneratorTest.java | 4 -- 4 files changed, 70 insertions(+), 6 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/jpa/test/integrationprovider/IntegrationProviderSettingByClassUsingPropertiesTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/boot/internal/EntityManagerFactoryBuilderImpl.java b/hibernate-core/src/main/java/org/hibernate/jpa/boot/internal/EntityManagerFactoryBuilderImpl.java index a64bde63f5..f83c4d56a3 100644 --- a/hibernate-core/src/main/java/org/hibernate/jpa/boot/internal/EntityManagerFactoryBuilderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/boot/internal/EntityManagerFactoryBuilderImpl.java @@ -196,8 +196,19 @@ public class EntityManagerFactoryBuilderImpl implements EntityManagerFactoryBuil integrationSettings = Collections.emptyMap(); } + Map mergedIntegrationSettings = null; + Properties properties = persistenceUnit.getProperties(); + if ( properties != null ) { + mergedIntegrationSettings = new HashMap( persistenceUnit.getProperties() ); + mergedIntegrationSettings.putAll( integrationSettings ); + } + // Build the boot-strap service registry, which mainly handles class loader interactions - final BootstrapServiceRegistry bsr = buildBootstrapServiceRegistry( integrationSettings, providedClassLoader, providedClassLoaderService); + final BootstrapServiceRegistry bsr = buildBootstrapServiceRegistry( + mergedIntegrationSettings != null ? mergedIntegrationSettings : integrationSettings, + providedClassLoader, + providedClassLoaderService + ); // merge configuration sources and build the "standard" service registry final StandardServiceRegistryBuilder ssrBuilder = StandardServiceRegistryBuilder.forJpa( bsr ); diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/BaseEntityManagerFunctionalTestCase.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/BaseEntityManagerFunctionalTestCase.java index 4f0648baac..36752b05b6 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/BaseEntityManagerFunctionalTestCase.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/BaseEntityManagerFunctionalTestCase.java @@ -80,7 +80,7 @@ public abstract class BaseEntityManagerFunctionalTestCase extends BaseUnitTestCa afterEntityManagerFactoryBuilt(); } - private PersistenceUnitDescriptor buildPersistenceUnitDescriptor() { + protected PersistenceUnitDescriptor buildPersistenceUnitDescriptor() { return new TestingPersistenceUnitDescriptorImpl( getClass().getSimpleName() ); } diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/integrationprovider/IntegrationProviderSettingByClassUsingPropertiesTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/integrationprovider/IntegrationProviderSettingByClassUsingPropertiesTest.java new file mode 100644 index 0000000000..cc096298db --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/integrationprovider/IntegrationProviderSettingByClassUsingPropertiesTest.java @@ -0,0 +1,57 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpa.test.integrationprovider; + +import java.util.List; +import java.util.Properties; + +import org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl; +import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Vlad Mihalcea + */ +@TestForIssue(jiraKey = "HHHH-13853") +public class IntegrationProviderSettingByClassUsingPropertiesTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class + }; + } + + @Test + public void test() { + doInJPA( this::entityManagerFactory, entityManager -> { + List dtos = entityManager.createQuery( + "select new PersonDto(id, name) " + + "from Person", PersonDto.class ) + .getResultList(); + } ); + } + + protected PersistenceUnitDescriptor buildPersistenceUnitDescriptor() { + return new TestingPersistenceUnitDescriptorImpl( getClass().getSimpleName() ) { + @Override + public Properties getProperties() { + Properties properties = new Properties(); + properties.put( + EntityManagerFactoryBuilderImpl.INTEGRATOR_PROVIDER, + DtoIntegratorProvider.class.getName() + ); + return properties; + } + }; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/schemagen/JpaSchemaGeneratorTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/schemagen/JpaSchemaGeneratorTest.java index b41e752284..7fde17a6aa 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/schemagen/JpaSchemaGeneratorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/schemagen/JpaSchemaGeneratorTest.java @@ -154,10 +154,6 @@ public class JpaSchemaGeneratorTest extends BaseEntityManagerFunctionalTestCase } } - private PersistenceUnitDescriptor buildPersistenceUnitDescriptor() { - return new TestingPersistenceUnitDescriptorImpl( getClass().getSimpleName() ); - } - /* Disable hibernate schema export */ @Override protected boolean createSchema() { From c346171b23a0d06fc74162a6b4846116c0d46c7d Mon Sep 17 00:00:00 2001 From: Vlad Mihalcea Date: Wed, 14 Nov 2018 18:32:14 +0200 Subject: [PATCH 02/27] HHH-13103 - Allow Hibernate Types to get access to the current configuration properties --- .../internal/util/ReflectHelper.java | 19 +++ .../java/org/hibernate/type/TypeFactory.java | 23 ++- .../type/spi/TypeBootstrapContext.java | 33 +++++ .../test/type/contributor/ArrayType.java | 13 ++ .../contributor/ArrayTypeContributorTest.java | 49 +++---- .../contributor/ArrayTypePropertiesTest.java | 131 ++++++++++++++++++ 6 files changed, 243 insertions(+), 25 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/type/spi/TypeBootstrapContext.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/type/contributor/ArrayTypePropertiesTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/ReflectHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/ReflectHelper.java index 423e38241e..bdade10f2d 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/ReflectHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/ReflectHelper.java @@ -347,6 +347,25 @@ public final class ReflectHelper { } + public static Constructor getConstructor( + Class clazz, + Class... constructorArgs) { + Constructor constructor = null; + try { + constructor = clazz.getDeclaredConstructor( constructorArgs ); + try { + ReflectHelper.ensureAccessibility( constructor ); + } + catch ( SecurityException e ) { + constructor = null; + } + } + catch ( NoSuchMethodException ignore ) { + } + + return constructor; + } + public static Method getMethod(Class clazz, Method method) { try { return clazz.getMethod( method.getName(), method.getParameterTypes() ); diff --git a/hibernate-core/src/main/java/org/hibernate/type/TypeFactory.java b/hibernate-core/src/main/java/org/hibernate/type/TypeFactory.java index 5e8b226f65..9eb349f1bc 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/TypeFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/type/TypeFactory.java @@ -7,15 +7,19 @@ package org.hibernate.type; import java.io.Serializable; +import java.lang.reflect.Constructor; import java.util.Comparator; +import java.util.Map; import java.util.Properties; import org.hibernate.MappingException; import org.hibernate.classic.Lifecycle; +import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.ReflectHelper; import org.hibernate.tuple.component.ComponentMetamodel; +import org.hibernate.type.spi.TypeBootstrapContext; import org.hibernate.type.spi.TypeConfiguration; import org.hibernate.type.spi.TypeConfigurationAware; import org.hibernate.usertype.CompositeUserType; @@ -88,7 +92,24 @@ public final class TypeFactory implements Serializable { public Type type(Class typeClass, Properties parameters) { try { - Type type = typeClass.newInstance(); + Type type; + + Constructor bootstrapContextAwareTypeConstructor = ReflectHelper.getConstructor( + typeClass, + TypeBootstrapContext.class + ); + if ( bootstrapContextAwareTypeConstructor != null ) { + ConfigurationService configurationService = typeConfiguration.getServiceRegistry().getService( + ConfigurationService.class ); + Map configurationSettings = configurationService.getSettings(); + type = bootstrapContextAwareTypeConstructor.newInstance( new TypeBootstrapContext( + configurationSettings + ) ); + } + else { + type = typeClass.newInstance(); + } + injectParameters( type, parameters ); return type; } diff --git a/hibernate-core/src/main/java/org/hibernate/type/spi/TypeBootstrapContext.java b/hibernate-core/src/main/java/org/hibernate/type/spi/TypeBootstrapContext.java new file mode 100644 index 0000000000..02853c8554 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/spi/TypeBootstrapContext.java @@ -0,0 +1,33 @@ +/* + * 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.type.spi; + +import java.util.Map; + +/** + * Provide a way to customize the {@link org.hibernate.type.Type} instantiation process. + *

+ * If a custom {@link org.hibernate.type.Type} defines a constructor which takes the + * {@link TypeBootstrapContext} argument, Hibernate will use this instead of the + * default constructor. + * + * @author Vlad Mihalcea + * + * @since 5.4 + */ +public class TypeBootstrapContext { + + private final Map configurationSettings; + + public TypeBootstrapContext(Map configurationSettings) { + this.configurationSettings = configurationSettings; + } + + public Map getConfigurationSettings() { + return configurationSettings; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/contributor/ArrayType.java b/hibernate-core/src/test/java/org/hibernate/test/type/contributor/ArrayType.java index 174523b220..e5e4328bd1 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/contributor/ArrayType.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/contributor/ArrayType.java @@ -1,9 +1,12 @@ package org.hibernate.test.type.contributor; +import java.util.Map; + import org.hibernate.dialect.Dialect; import org.hibernate.type.AbstractSingleColumnStandardBasicType; import org.hibernate.type.DiscriminatorType; import org.hibernate.type.descriptor.sql.VarcharTypeDescriptor; +import org.hibernate.type.spi.TypeBootstrapContext; /** * @author Vlad Mihalcea @@ -14,10 +17,17 @@ public class ArrayType public static final ArrayType INSTANCE = new ArrayType(); + private Map settings; + public ArrayType() { super( VarcharTypeDescriptor.INSTANCE, ArrayTypeDescriptor.INSTANCE ); } + public ArrayType(TypeBootstrapContext typeBootstrapContext) { + super( VarcharTypeDescriptor.INSTANCE, ArrayTypeDescriptor.INSTANCE ); + this.settings = typeBootstrapContext.getConfigurationSettings(); + } + @Override public Array stringToObject(String xml) throws Exception { return fromString( xml ); @@ -33,4 +43,7 @@ public class ArrayType return "comma-separated-array"; } + public Map getSettings() { + return settings; + } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/contributor/ArrayTypeContributorTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/contributor/ArrayTypeContributorTest.java index 10d687b5d5..1f414e45d7 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/type/contributor/ArrayTypeContributorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/type/contributor/ArrayTypeContributorTest.java @@ -6,29 +6,33 @@ */ package org.hibernate.test.type.contributor; +import java.util.Arrays; import java.util.List; +import java.util.Map; +import java.util.Properties; import javax.persistence.Entity; import javax.persistence.Id; -import javax.persistence.Table; +import org.hibernate.Session; import org.hibernate.annotations.Type; -import org.hibernate.cfg.Configuration; +import org.hibernate.boot.spi.MetadataBuilderContributor; +import org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.query.Query; import org.hibernate.testing.TestForIssue; -import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; -import org.hibernate.test.collection.custom.basic.MyList; import org.junit.Test; -import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +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; /** * @author Vlad Mihalcea */ @TestForIssue( jiraKey = "HHH-11409" ) -public class ArrayTypeContributorTest extends BaseCoreFunctionalTestCase { +public class ArrayTypeContributorTest extends BaseEntityManagerFunctionalTestCase { @Override protected Class[] getAnnotatedClasses() { @@ -36,36 +40,33 @@ public class ArrayTypeContributorTest extends BaseCoreFunctionalTestCase { } @Override - protected Configuration constructAndConfigureConfiguration() { - Configuration configuration = super.constructAndConfigureConfiguration(); - configuration.registerTypeContributor( (typeContributions, serviceRegistry) -> { - typeContributions.contributeType( ArrayType.INSTANCE ); - } ); - return configuration; + protected void addConfigOptions(Map options) { + options.put( + EntityManagerFactoryBuilderImpl.METADATA_BUILDER_CONTRIBUTOR, + (MetadataBuilderContributor) metadataBuilder -> + metadataBuilder.applyTypes( (typeContributions, serviceRegistry) -> { + typeContributions.contributeType( ArrayType.INSTANCE ); + } )); } @Override - protected void prepareTest() throws Exception { - doInHibernate( this::sessionFactory, session -> { + protected void afterEntityManagerFactoryBuilt() { + doInJPA( this::entityManagerFactory, entityManager -> { CorporateUser user = new CorporateUser(); user.setUserName( "Vlad" ); - session.persist( user ); + entityManager.persist( user ); user.getEmailAddresses().add( "vlad@hibernate.info" ); user.getEmailAddresses().add( "vlad@hibernate.net" ); } ); } - @Override - protected boolean isCleanupTestDataRequired() { - return true; - } - @Test public void test() { - doInHibernate( this::sessionFactory, session -> { - List users = session.createQuery( + doInJPA( this::entityManagerFactory, entityManager -> { + List users = entityManager.createQuery( "select u from CorporateUser u where u.emailAddresses = :address", CorporateUser.class ) + .unwrap( Query.class ) .setParameter( "address", new Array(), ArrayType.INSTANCE ) .getResultList(); @@ -75,8 +76,8 @@ public class ArrayTypeContributorTest extends BaseCoreFunctionalTestCase { @Test public void testNativeSQL() { - doInHibernate( this::sessionFactory, session -> { - List emails = session.createNativeQuery( + doInJPA( this::entityManagerFactory, entityManager -> { + List emails = entityManager.createNativeQuery( "select u.emailAddresses from CorporateUser u where u.userName = :name" ) .setParameter( "name", "Vlad" ) .getResultList(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/contributor/ArrayTypePropertiesTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/contributor/ArrayTypePropertiesTest.java new file mode 100644 index 0000000000..1f17b0d305 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/type/contributor/ArrayTypePropertiesTest.java @@ -0,0 +1,131 @@ +/* + * 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.type.contributor; + +import java.util.List; +import java.util.Map; +import java.util.Properties; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.transaction.Transactional; + +import org.hibernate.Session; +import org.hibernate.annotations.Type; +import org.hibernate.annotations.TypeDef; +import org.hibernate.boot.spi.MetadataBuilderContributor; +import org.hibernate.engine.spi.EntityEntry; +import org.hibernate.engine.spi.PersistenceContext; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.query.Query; + +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * @author Vlad Mihalcea + */ +@TestForIssue( jiraKey = "HHH-13103" ) +public class ArrayTypePropertiesTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { CorporateUser.class }; + } + + @Override + protected void addConfigOptions(Map options) { + options.put( "hibernate.type.array.config", new Integer[]{1, 2, 3} ); + } + + @Override + protected void afterEntityManagerFactoryBuilt() { + doInJPA( this::entityManagerFactory, entityManager -> { + CorporateUser user = new CorporateUser(); + user.setUserName( "Vlad" ); + entityManager.persist( user ); + + user.getEmailAddresses().add( "vlad@hibernate.info" ); + user.getEmailAddresses().add( "vlad@hibernate.net" ); + } ); + } + + @Test + public void test() { + doInJPA( this::entityManagerFactory, entityManager -> { + List users = entityManager.createQuery( + "select u from CorporateUser u where u.emailAddresses = :address", CorporateUser.class ) + .unwrap( Query.class ) + .setParameter( "address", new Array(), ArrayType.INSTANCE ) + .getResultList(); + + assertTrue( users.isEmpty() ); + } ); + } + + @Test + public void testNativeSQL() { + doInJPA( this::entityManagerFactory, entityManager -> { + List emails = entityManager.createNativeQuery( + "select u.emailAddresses from CorporateUser u where u.userName = :name" ) + .setParameter( "name", "Vlad" ) + .getResultList(); + + assertEquals( 1, emails.size() ); + } ); + } + + @Test + public void testConfigurationSettings() { + doInJPA( this::entityManagerFactory, entityManager -> { + SharedSessionContractImplementor session = entityManager.unwrap( SharedSessionContractImplementor.class ); + + CorporateUser corporateUser = entityManager.find( CorporateUser.class, "Vlad" ); + PersistenceContext persistenceContext = session.getPersistenceContext(); + EntityPersister entityPersister = persistenceContext.getEntry( corporateUser ).getPersister(); + ArrayType arrayType = (ArrayType) entityPersister.getPropertyType( "emailAddresses" ); + + Map settings = arrayType.getSettings(); + Integer[] arrayConfig = (Integer[]) settings.get( "hibernate.type.array.config" ); + assertNotNull( arrayConfig ); + assertArrayEquals( new Integer[]{1, 2, 3}, arrayConfig ); + } ); + } + + @Entity(name = "CorporateUser") + @TypeDef( typeClass = ArrayType.class, defaultForType = Array.class ) + public static class CorporateUser { + + @Id + private String userName; + + private Array emailAddresses = new Array(); + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public Array getEmailAddresses() { + return emailAddresses; + } + } + + +} + From 1042f23beee20777c1e359b910a1c59d9756c2f2 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Tue, 18 Feb 2020 15:21:29 -0600 Subject: [PATCH 03/27] HHH-13103 - Allow Hibernate Types to get access to the current configuration properties using constructor injection --- .../java/org/hibernate/type/TypeFactory.java | 25 ++++++++----------- .../type/spi/TypeBootstrapContext.java | 13 ++-------- 2 files changed, 12 insertions(+), 26 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/type/TypeFactory.java b/hibernate-core/src/main/java/org/hibernate/type/TypeFactory.java index 9eb349f1bc..9acb4bc484 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/TypeFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/type/TypeFactory.java @@ -16,7 +16,6 @@ import org.hibernate.MappingException; import org.hibernate.classic.Lifecycle; import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.ReflectHelper; import org.hibernate.tuple.component.ComponentMetamodel; import org.hibernate.type.spi.TypeBootstrapContext; @@ -26,8 +25,6 @@ import org.hibernate.usertype.CompositeUserType; import org.hibernate.usertype.ParameterizedType; import org.hibernate.usertype.UserType; -import static org.hibernate.internal.CoreLogging.messageLogger; - /** * Used internally to build instances of {@link Type}, specifically it builds instances of *

@@ -42,9 +39,7 @@ import static org.hibernate.internal.CoreLogging.messageLogger; */ @Deprecated @SuppressWarnings({"unchecked"}) -public final class TypeFactory implements Serializable { - private static final CoreMessageLogger LOG = messageLogger( TypeFactory.class ); - +public final class TypeFactory implements Serializable, TypeBootstrapContext { /** * @deprecated Use {@link TypeConfiguration}/{@link TypeConfiguration.Scope} instead */ @@ -61,6 +56,11 @@ public final class TypeFactory implements Serializable { this.typeScope = (TypeScope) () -> typeConfiguration; } + @Override + public Map getConfigurationSettings() { + return typeConfiguration.getServiceRegistry().getService( ConfigurationService.class ).getSettings(); + } + public SessionFactoryImplementor resolveSessionFactory() { return typeConfiguration.getSessionFactory(); } @@ -92,25 +92,21 @@ public final class TypeFactory implements Serializable { public Type type(Class typeClass, Properties parameters) { try { - Type type; + final Type type; - Constructor bootstrapContextAwareTypeConstructor = ReflectHelper.getConstructor( + final Constructor bootstrapContextAwareTypeConstructor = ReflectHelper.getConstructor( typeClass, TypeBootstrapContext.class ); if ( bootstrapContextAwareTypeConstructor != null ) { - ConfigurationService configurationService = typeConfiguration.getServiceRegistry().getService( - ConfigurationService.class ); - Map configurationSettings = configurationService.getSettings(); - type = bootstrapContextAwareTypeConstructor.newInstance( new TypeBootstrapContext( - configurationSettings - ) ); + type = bootstrapContextAwareTypeConstructor.newInstance( this ); } else { type = typeClass.newInstance(); } injectParameters( type, parameters ); + return type; } catch (Exception e) { @@ -143,7 +139,6 @@ public final class TypeFactory implements Serializable { * @deprecated Only for use temporary use by {@link org.hibernate.Hibernate} */ @Deprecated - @SuppressWarnings({"JavaDoc"}) public static CompositeCustomType customComponent( Class typeClass, Properties parameters, diff --git a/hibernate-core/src/main/java/org/hibernate/type/spi/TypeBootstrapContext.java b/hibernate-core/src/main/java/org/hibernate/type/spi/TypeBootstrapContext.java index 02853c8554..4d1d3a49b2 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/spi/TypeBootstrapContext.java +++ b/hibernate-core/src/main/java/org/hibernate/type/spi/TypeBootstrapContext.java @@ -19,15 +19,6 @@ import java.util.Map; * * @since 5.4 */ -public class TypeBootstrapContext { - - private final Map configurationSettings; - - public TypeBootstrapContext(Map configurationSettings) { - this.configurationSettings = configurationSettings; - } - - public Map getConfigurationSettings() { - return configurationSettings; - } +public interface TypeBootstrapContext { + Map getConfigurationSettings(); } From 5ea5bd12b07ad7915e97dd05b1a2ab5194f73ab8 Mon Sep 17 00:00:00 2001 From: Christoph Dreis Date: Wed, 19 Feb 2020 16:18:03 +0100 Subject: [PATCH 04/27] HHH-13870 - Extract task action in Gradle plugin to fix up-to-date checks Lambdas are not allowed and cause the compile task to be always out of date --- .../orm/tooling/gradle/HibernatePlugin.java | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/tooling/hibernate-gradle-plugin/src/main/groovy/org/hibernate/orm/tooling/gradle/HibernatePlugin.java b/tooling/hibernate-gradle-plugin/src/main/groovy/org/hibernate/orm/tooling/gradle/HibernatePlugin.java index 48ec320c12..36ea046728 100644 --- a/tooling/hibernate-gradle-plugin/src/main/groovy/org/hibernate/orm/tooling/gradle/HibernatePlugin.java +++ b/tooling/hibernate-gradle-plugin/src/main/groovy/org/hibernate/orm/tooling/gradle/HibernatePlugin.java @@ -6,6 +6,7 @@ */ package org.hibernate.orm.tooling.gradle; +import org.gradle.api.Action; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.Task; @@ -43,10 +44,29 @@ public class HibernatePlugin implements Plugin { final Task compileTask = project.getTasks().findByName( sourceSet.getCompileJavaTaskName() ); assert compileTask != null; - compileTask.doLast( - task -> EnhancementHelper.enhance( sourceSet, hibernateExtension.enhance, project ) - ); + compileTask.doLast(new EnhancerAction( sourceSet, hibernateExtension, project )); } } + private static class EnhancerAction implements Action { + + private final SourceSet sourceSet; + + private final HibernateExtension hibernateExtension; + + private final Project project; + + private EnhancerAction(SourceSet sourceSet, HibernateExtension hibernateExtension, Project project) { + this.sourceSet = sourceSet; + this.hibernateExtension = hibernateExtension; + this.project = project; + } + + @Override + public void execute(Task task) { + EnhancementHelper.enhance( sourceSet, hibernateExtension.enhance, project ); + } + + } + } From 1364929fe08f87400ee97a4859d4c242c07c32fd Mon Sep 17 00:00:00 2001 From: Christoph Dreis Date: Wed, 19 Feb 2020 17:47:05 +0100 Subject: [PATCH 05/27] HHH-13870 - Add explaining comment --- .../org/hibernate/orm/tooling/gradle/HibernatePlugin.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tooling/hibernate-gradle-plugin/src/main/groovy/org/hibernate/orm/tooling/gradle/HibernatePlugin.java b/tooling/hibernate-gradle-plugin/src/main/groovy/org/hibernate/orm/tooling/gradle/HibernatePlugin.java index 36ea046728..d35037fc40 100644 --- a/tooling/hibernate-gradle-plugin/src/main/groovy/org/hibernate/orm/tooling/gradle/HibernatePlugin.java +++ b/tooling/hibernate-gradle-plugin/src/main/groovy/org/hibernate/orm/tooling/gradle/HibernatePlugin.java @@ -48,6 +48,12 @@ public class HibernatePlugin implements Plugin { } } + /** + * Gradle doesn't allow lambdas in doLast or doFirst configurations and causing up-to-date checks + * to fail. Extracting the lambda to an inner class works around this issue. + * + * @link https://github.com/gradle/gradle/issues/5510 + */ private static class EnhancerAction implements Action { private final SourceSet sourceSet; From 28b8cebf9d730b948736b732ef95d43c088144b6 Mon Sep 17 00:00:00 2001 From: w1ida Date: Wed, 12 Feb 2020 11:09:29 +0800 Subject: [PATCH 06/27] HHH-13873 IdTableHelper can skip opening a connection when there's no statements to execute --- .../src/main/java/org/hibernate/hql/spi/id/IdTableHelper.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/IdTableHelper.java b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/IdTableHelper.java index 2bf7478bf9..e0299a5baa 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/spi/id/IdTableHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/spi/id/IdTableHelper.java @@ -61,6 +61,9 @@ public class IdTableHelper { List creationStatements, JdbcServices jdbcServices, JdbcConnectionAccess connectionAccess) { + if ( creationStatements == null || creationStatements.isEmpty() ) { + return; + } try { Connection connection; try { From 3c67d521e1c79421a7282120ee429179d74183e7 Mon Sep 17 00:00:00 2001 From: "Arend v. Reinersdorff" Date: Sun, 16 Feb 2020 14:11:57 +0100 Subject: [PATCH 07/27] Fix typo "fare more" -> "far more" --- .../asciidoc/userguide/chapters/portability/Portability.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/src/main/asciidoc/userguide/chapters/portability/Portability.adoc b/documentation/src/main/asciidoc/userguide/chapters/portability/Portability.adoc index d3680f464b..1e8cf2fed1 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/portability/Portability.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/portability/Portability.adoc @@ -26,7 +26,7 @@ Generally, this required their users to configure the Hibernate dialect or defin Starting with version 3.2, Hibernate introduced the notion of automatically detecting the dialect to use based on the `java.sql.DatabaseMetaData` obtained from a `java.sql.Connection` to that database. This was much better, except that this resolution was limited to databases Hibernate know about ahead of time and was in no way configurable or overrideable. -Starting with version 3.3, Hibernate has a fare more powerful way to automatically determine which dialect to be used by relying on a series of delegates which implement the `org.hibernate.dialect.resolver.DialectResolver` which defines only a single method: +Starting with version 3.3, Hibernate has a far more powerful way to automatically determine which dialect to be used by relying on a series of delegates which implement the `org.hibernate.dialect.resolver.DialectResolver` which defines only a single method: [source,java] ---- From d9a335c429ef812bc9726e9d467597ebae2b81a5 Mon Sep 17 00:00:00 2001 From: Vlad Mihalcea Date: Thu, 20 Feb 2020 11:16:11 +0200 Subject: [PATCH 08/27] HHH-13872 - Make the Java Stream close the underlying ScrollableResultsIterator upon calling a terminal operation --- .../userguide/chapters/query/hql/HQL.adoc | 35 +- .../org/hibernate/userguide/hql/HQLTest.java | 34 +- .../internal/util/ReflectHelper.java | 9 + .../query/internal/AbstractProducedQuery.java | 9 +- .../query/spi/DoubleStreamDecorator.java | 318 ++++++++++++++++ .../query/spi/IntStreamDecorator.java | 333 +++++++++++++++++ .../query/spi/LongStreamDecorator.java | 326 +++++++++++++++++ .../hibernate/query/spi/StreamDecorator.java | 340 ++++++++++++++++++ .../test/stream/basic/BasicStreamTest.java | 2 +- .../test/stream/basic/JpaStreamTest.java | 191 +++++++++- 10 files changed, 1582 insertions(+), 15 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/query/spi/DoubleStreamDecorator.java create mode 100644 hibernate-core/src/main/java/org/hibernate/query/spi/IntStreamDecorator.java create mode 100644 hibernate-core/src/main/java/org/hibernate/query/spi/LongStreamDecorator.java create mode 100644 hibernate-core/src/main/java/org/hibernate/query/spi/StreamDecorator.java diff --git a/documentation/src/main/asciidoc/userguide/chapters/query/hql/HQL.adoc b/documentation/src/main/asciidoc/userguide/chapters/query/hql/HQL.adoc index 2db548b9ee..17902a1f96 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/query/hql/HQL.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/query/hql/HQL.adoc @@ -165,9 +165,10 @@ include::{sourcedir}/HQLTest.java[tags=jpql-api-positional-parameter-example] It's good practice not to mix parameter binding forms in a given query. ==== -In terms of execution, JPA `Query` offers 2 different methods for retrieving a result set. +In terms of execution, JPA `Query` offers 3 different methods for retrieving a result set. * `Query#getResultList()` - executes the select query and returns back the list of results. +* `Query#getResultStream()` - executes the select query and returns back a `Stream` over the results. * `Query#getSingleResult()` - executes the select query and returns a single result. If there were more than one result an exception is thrown. [[jpql-api-list-example]] @@ -179,6 +180,15 @@ include::{sourcedir}/HQLTest.java[tags=jpql-api-list-example] ---- ==== +[[jpql-api-stream-example]] +.JPA `getResultStream()` result +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/HQLTest.java[tags=jpql-api-stream-example] +---- +==== + [[jpql-api-unique-result-example]] .JPA `getSingleResult()` ==== @@ -414,6 +424,29 @@ include::{sourcedir}/HQLTest.java[tags=hql-api-stream-example] Just like with `ScrollableResults`, you should always close a Hibernate `Stream` either explicitly or using a https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html[try-with-resources] block. ==== +[[jpql-api-stream]] +==== Query streaming + +Since version 2.2, the JPA `Query` interface offers support for returning a `Stream` via the `getResultStream` method. + +Just like the `scroll` method, you can use a try-with-resources block to close the `Stream` +prior to closing the currently running Persistence Context. + +Since Hibernate 5.4, the `Stream` is also closed when calling a terminal operation, +as illustrated by the following example. + +[[jpql-api-stream-terminal-operation]] +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/HQLTest.java[tags=jpql-api-stream-terminal-operation] +---- +==== + +The `Stream` is closed automatically after calling the `collect` method, +since there is no reason to keep the underlying JDBC `ResultSet` open +if the `Stream` cannot be reused. + [[hql-case-sensitivity]] === Case Sensitivity diff --git a/documentation/src/test/java/org/hibernate/userguide/hql/HQLTest.java b/documentation/src/test/java/org/hibernate/userguide/hql/HQLTest.java index 702ed53632..9867a70d0a 100644 --- a/documentation/src/test/java/org/hibernate/userguide/hql/HQLTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/hql/HQLTest.java @@ -10,7 +10,6 @@ import java.math.BigDecimal; import java.sql.Timestamp; import java.time.LocalDateTime; import java.time.ZoneOffset; -import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.List; @@ -43,7 +42,6 @@ import org.hibernate.userguide.model.PhoneType; import org.hibernate.userguide.model.WireTransferPayment; import org.hibernate.testing.DialectChecks; -import org.hibernate.testing.FailureExpected; import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.SkipForDialect; @@ -673,6 +671,38 @@ public class HQLTest extends BaseEntityManagerFunctionalTestCase { }); } + @Test + public void test_jpql_api_stream_example() { + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::jpql-api-stream-example[] + try(Stream personStream = entityManager.createQuery( + "select p " + + "from Person p " + + "where p.name like :name", Person.class ) + .setParameter( "name", "J%" ) + .getResultStream()) { + List persons = personStream + .skip( 5 ) + .limit( 5 ) + .collect( Collectors.toList() ); + } + //end::jpql-api-stream-example[] + + // tag::jpql-api-stream-terminal-operation[] + List persons = entityManager.createQuery( + "select p " + + "from Person p " + + "where p.name like :name", Person.class ) + .setParameter( "name", "J%" ) + .getResultStream() + .skip( 5 ) + .limit( 5 ) + .collect( Collectors.toList() ); + + //end::jpql-api-stream-terminal-operation[] + }); + } + @Test public void test_jpql_api_single_result_example() { doInJPA( this::entityManagerFactory, entityManager -> { diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/ReflectHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/ReflectHelper.java index bdade10f2d..be5a0caacc 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/ReflectHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/ReflectHelper.java @@ -375,6 +375,15 @@ public final class ReflectHelper { } } + public static Method getMethod(Class clazz, String methodName, Class... paramTypes) { + try { + return clazz.getMethod( methodName, paramTypes ); + } + catch (Exception e) { + return null; + } + } + public static Field findField(Class containerClass, String propertyName) { if ( containerClass == null ) { throw new IllegalArgumentException( "Class on which to find field [" + propertyName + "] cannot be null" ); 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 374410912a..2c0eafd11a 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 @@ -56,6 +56,7 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.TypedValue; import org.hibernate.graph.GraphSemantic; import org.hibernate.graph.RootGraph; +import org.hibernate.graph.internal.RootGraphImpl; import org.hibernate.graph.spi.RootGraphImplementor; import org.hibernate.hql.internal.QueryExecutionRequestException; import org.hibernate.internal.EmptyScrollableResults; @@ -64,7 +65,6 @@ import org.hibernate.internal.HEMLogging; import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.jpa.QueryHints; import org.hibernate.jpa.TypedParameterValue; -import org.hibernate.graph.internal.RootGraphImpl; import org.hibernate.jpa.internal.util.CacheModeHelper; import org.hibernate.jpa.internal.util.ConfigurationHelper; import org.hibernate.jpa.internal.util.FlushModeTypeHelper; @@ -80,6 +80,7 @@ import org.hibernate.query.spi.QueryParameterBinding; import org.hibernate.query.spi.QueryParameterBindings; import org.hibernate.query.spi.QueryParameterListBinding; import org.hibernate.query.spi.ScrollableResultsImplementor; +import org.hibernate.query.spi.StreamDecorator; import org.hibernate.transform.ResultTransformer; import org.hibernate.type.Type; @@ -1512,8 +1513,10 @@ public abstract class AbstractProducedQuery implements QueryImplementor { final ScrollableResultsIterator iterator = new ScrollableResultsIterator<>( scrollableResults ); final Spliterator spliterator = Spliterators.spliteratorUnknownSize( iterator, Spliterator.NONNULL ); - final Stream stream = StreamSupport.stream( spliterator, false ); - stream.onClose( scrollableResults::close ); + final Stream stream = new StreamDecorator( + StreamSupport.stream( spliterator, false ), + scrollableResults::close + ); return stream; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/spi/DoubleStreamDecorator.java b/hibernate-core/src/main/java/org/hibernate/query/spi/DoubleStreamDecorator.java new file mode 100644 index 0000000000..9035a68289 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/spi/DoubleStreamDecorator.java @@ -0,0 +1,318 @@ +/* + * 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.query.spi; + +import java.util.DoubleSummaryStatistics; +import java.util.OptionalDouble; +import java.util.PrimitiveIterator; +import java.util.Spliterator; +import java.util.function.BiConsumer; +import java.util.function.DoubleBinaryOperator; +import java.util.function.DoubleConsumer; +import java.util.function.DoubleFunction; +import java.util.function.DoublePredicate; +import java.util.function.DoubleToIntFunction; +import java.util.function.DoubleToLongFunction; +import java.util.function.DoubleUnaryOperator; +import java.util.function.ObjDoubleConsumer; +import java.util.function.Supplier; +import java.util.stream.DoubleStream; +import java.util.stream.IntStream; +import java.util.stream.LongStream; +import java.util.stream.Stream; + +import org.hibernate.Incubating; + +/** + * The {@link DoubleStreamDecorator} wraps a Java {@link DoubleStream} and registers a {@code closeHandler} + * which is passed further to any resulting {@link Stream}. + *

+ * The goal of the {@link DoubleStreamDecorator} is to close the underlying {@link DoubleStream} upon + * calling a terminal operation. + * + * @author Vlad Mihalcea + * @since 5.4 + */ +@Incubating +public class DoubleStreamDecorator implements DoubleStream { + + private final DoubleStream delegate; + + private Runnable closeHandler; + + public DoubleStreamDecorator( + DoubleStream delegate, + Runnable closeHandler) { + this.delegate = delegate; + this.closeHandler = closeHandler; + this.delegate.onClose( closeHandler ); + } + + @Override + public DoubleStream filter(DoublePredicate predicate) { + return new DoubleStreamDecorator( + delegate.filter( predicate ), + closeHandler + ); + } + + @Override + public DoubleStream map(DoubleUnaryOperator mapper) { + return new DoubleStreamDecorator( + delegate.map( mapper ), + closeHandler + ); + } + + @Override + public Stream mapToObj(DoubleFunction mapper) { + return new StreamDecorator<>( + delegate.mapToObj( mapper ), + closeHandler + ); + } + + @Override + public IntStream mapToInt(DoubleToIntFunction mapper) { + return new IntStreamDecorator( + delegate.mapToInt( mapper ), + closeHandler + ); + } + + @Override + public LongStream mapToLong(DoubleToLongFunction mapper) { + return new LongStreamDecorator( + delegate.mapToLong( mapper ), + closeHandler + ); + } + + @Override + public DoubleStream flatMap(DoubleFunction mapper) { + return new DoubleStreamDecorator( + delegate.flatMap( mapper ), + closeHandler + ); + } + + @Override + public DoubleStream distinct() { + return new DoubleStreamDecorator( + delegate.distinct(), + closeHandler + ); + } + + @Override + public DoubleStream sorted() { + return new DoubleStreamDecorator( + delegate.sorted(), + closeHandler + ); + } + + @Override + public DoubleStream peek(DoubleConsumer action) { + return new DoubleStreamDecorator( + delegate.peek( action ), + closeHandler + ); + } + + @Override + public DoubleStream limit(long maxSize) { + return new DoubleStreamDecorator( + delegate.limit( maxSize ), + closeHandler + ); + } + + @Override + public DoubleStream skip(long n) { + return new DoubleStreamDecorator( + delegate.skip( n ), + closeHandler + ); + } + + @Override + public void forEach(DoubleConsumer action) { + delegate.forEach( action ); + close(); + } + + @Override + public void forEachOrdered(DoubleConsumer action) { + delegate.forEachOrdered( action ); + close(); + } + + @Override + public double[] toArray() { + double[] result = delegate.toArray(); + close(); + return result; + } + + @Override + public double reduce(double identity, DoubleBinaryOperator op) { + double result = delegate.reduce( identity, op ); + close(); + return result; + } + + @Override + public OptionalDouble reduce(DoubleBinaryOperator op) { + OptionalDouble result = delegate.reduce( op ); + close(); + return result; + } + + @Override + public R collect( + Supplier supplier, ObjDoubleConsumer accumulator, BiConsumer combiner) { + R result = delegate.collect( supplier, accumulator, combiner ); + close(); + return result; + } + + @Override + public double sum() { + double result = delegate.sum(); + close(); + return result; + } + + @Override + public OptionalDouble min() { + OptionalDouble result = delegate.min(); + close(); + return result; + } + + @Override + public OptionalDouble max() { + OptionalDouble result = delegate.max(); + close(); + return result; + } + + @Override + public long count() { + long result = delegate.count(); + close(); + return result; + } + + @Override + public OptionalDouble average() { + OptionalDouble result = delegate.average(); + close(); + return result; + } + + @Override + public DoubleSummaryStatistics summaryStatistics() { + DoubleSummaryStatistics result = delegate.summaryStatistics(); + close(); + return result; + } + + @Override + public boolean anyMatch(DoublePredicate predicate) { + boolean result = delegate.anyMatch( predicate ); + close(); + return result; + } + + @Override + public boolean allMatch(DoublePredicate predicate) { + boolean result = delegate.allMatch( predicate ); + close(); + return result; + } + + @Override + public boolean noneMatch(DoublePredicate predicate) { + boolean result = delegate.noneMatch( predicate ); + close(); + return result; + } + + @Override + public OptionalDouble findFirst() { + OptionalDouble result = delegate.findFirst(); + close(); + return result; + } + + @Override + public OptionalDouble findAny() { + OptionalDouble result = delegate.findAny(); + close(); + return result; + } + + @Override + public Stream boxed() { + return new StreamDecorator<>( + delegate.boxed(), + closeHandler + ); + } + + @Override + public DoubleStream sequential() { + return new DoubleStreamDecorator( + delegate.sequential(), + closeHandler + ); + } + + @Override + public DoubleStream parallel() { + return new DoubleStreamDecorator( + delegate.parallel(), + closeHandler + ); + } + + @Override + public DoubleStream unordered() { + return new DoubleStreamDecorator( + delegate.unordered(), + closeHandler + ); + } + + @Override + public DoubleStream onClose(Runnable closeHandler) { + this.closeHandler = closeHandler; + return this; + } + + @Override + public void close() { + delegate.close(); + } + + @Override + public PrimitiveIterator.OfDouble iterator() { + return delegate.iterator(); + } + + @Override + public Spliterator.OfDouble spliterator() { + return delegate.spliterator(); + } + + @Override + public boolean isParallel() { + return delegate.isParallel(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/spi/IntStreamDecorator.java b/hibernate-core/src/main/java/org/hibernate/query/spi/IntStreamDecorator.java new file mode 100644 index 0000000000..d8050af658 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/spi/IntStreamDecorator.java @@ -0,0 +1,333 @@ +/* + * 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.query.spi; + +import java.util.IntSummaryStatistics; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.PrimitiveIterator; +import java.util.Spliterator; +import java.util.function.BiConsumer; +import java.util.function.IntBinaryOperator; +import java.util.function.IntConsumer; +import java.util.function.IntFunction; +import java.util.function.IntPredicate; +import java.util.function.IntToDoubleFunction; +import java.util.function.IntToLongFunction; +import java.util.function.IntUnaryOperator; +import java.util.function.ObjIntConsumer; +import java.util.function.Supplier; +import java.util.stream.DoubleStream; +import java.util.stream.IntStream; +import java.util.stream.LongStream; +import java.util.stream.Stream; + +import org.hibernate.Incubating; + +/** + * The {@link IntStreamDecorator} wraps a Java {@link IntStream} and registers a {@code closeHandler} + * which is passed further to any resulting {@link Stream}. + *

+ * The goal of the {@link IntStreamDecorator} is to close the underlying {@link IntStream} upon + * calling a terminal operation. + * + * @author Vlad Mihalcea + * @since 5.4 + */ +@Incubating +public class IntStreamDecorator implements IntStream { + + private final IntStream delegate; + + private Runnable closeHandler; + + public IntStreamDecorator( + IntStream delegate, + Runnable closeHandler) { + this.delegate = delegate; + this.closeHandler = closeHandler; + this.delegate.onClose( closeHandler ); + } + + @Override + public IntStream filter(IntPredicate predicate) { + return new IntStreamDecorator( + delegate.filter( predicate ), + closeHandler + ); + } + + @Override + public IntStream map(IntUnaryOperator mapper) { + return new IntStreamDecorator( + delegate.map( mapper ), + closeHandler + ); + } + + @Override + public Stream mapToObj(IntFunction mapper) { + return new StreamDecorator<>( + delegate.mapToObj( mapper ), + closeHandler + ); + } + + @Override + public LongStream mapToLong(IntToLongFunction mapper) { + return new LongStreamDecorator( + delegate.mapToLong( mapper ), + closeHandler + ); + } + + @Override + public DoubleStream mapToDouble(IntToDoubleFunction mapper) { + return new DoubleStreamDecorator( + delegate.mapToDouble( mapper ), + closeHandler + ); + } + + @Override + public IntStream flatMap(IntFunction mapper) { + return new IntStreamDecorator( + delegate.flatMap( mapper ), + closeHandler + ); + } + + @Override + public IntStream distinct() { + return new IntStreamDecorator( + delegate.distinct(), + closeHandler + ); + } + + @Override + public IntStream sorted() { + return new IntStreamDecorator( + delegate.sorted(), + closeHandler + ); + } + + @Override + public IntStream peek(IntConsumer action) { + return new IntStreamDecorator( + delegate.peek( action ), + closeHandler + ); + } + + @Override + public IntStream limit(long maxSize) { + return new IntStreamDecorator( + delegate.limit( maxSize ), + closeHandler + ); + } + + @Override + public IntStream skip(long n) { + return new IntStreamDecorator( + delegate.skip( n ), + closeHandler + ); + } + + @Override + public void forEach(IntConsumer action) { + delegate.forEach( action ); + close(); + } + + @Override + public void forEachOrdered(IntConsumer action) { + delegate.forEachOrdered( action ); + close(); + } + + @Override + public int[] toArray() { + int[] result = delegate.toArray(); + close(); + return result; + } + + @Override + public int reduce(int identity, IntBinaryOperator op) { + int result = delegate.reduce( identity, op ); + close(); + return result; + } + + @Override + public OptionalInt reduce(IntBinaryOperator op) { + OptionalInt result = delegate.reduce( op ); + close(); + return result; + } + + @Override + public R collect( + Supplier supplier, ObjIntConsumer accumulator, BiConsumer combiner) { + R result = delegate.collect( supplier, accumulator, combiner ); + close(); + return result; + } + + @Override + public int sum() { + int result = delegate.sum(); + close(); + return result; + } + + @Override + public OptionalInt min() { + OptionalInt result = delegate.min(); + close(); + return result; + } + + @Override + public OptionalInt max() { + OptionalInt result = delegate.max(); + close(); + return result; + } + + @Override + public long count() { + long result = delegate.count(); + close(); + return result; + } + + @Override + public OptionalDouble average() { + OptionalDouble result = delegate.average(); + close(); + return result; + } + + @Override + public IntSummaryStatistics summaryStatistics() { + IntSummaryStatistics result = delegate.summaryStatistics(); + close(); + return result; + } + + @Override + public boolean anyMatch(IntPredicate predicate) { + boolean result = delegate.anyMatch( predicate ); + close(); + return result; + } + + @Override + public boolean allMatch(IntPredicate predicate) { + boolean result = delegate.allMatch( predicate ); + close(); + return result; + } + + @Override + public boolean noneMatch(IntPredicate predicate) { + boolean result = delegate.noneMatch( predicate ); + close(); + return result; + } + + @Override + public OptionalInt findFirst() { + OptionalInt result = delegate.findFirst(); + close(); + return result; + } + + @Override + public OptionalInt findAny() { + OptionalInt result = delegate.findAny(); + close(); + return result; + } + + @Override + public LongStream asLongStream() { + LongStream result = delegate.asLongStream(); + close(); + return result; + } + + @Override + public DoubleStream asDoubleStream() { + DoubleStream result = delegate.asDoubleStream(); + close(); + return result; + } + + @Override + public Stream boxed() { + return new StreamDecorator<>( + delegate.boxed(), + closeHandler + ); + } + + @Override + public IntStream sequential() { + return new IntStreamDecorator( + delegate.sequential(), + closeHandler + ); + } + + @Override + public IntStream parallel() { + return new IntStreamDecorator( + delegate.parallel(), + closeHandler + ); + } + + @Override + public IntStream unordered() { + return new IntStreamDecorator( + delegate.unordered(), + closeHandler + ); + } + + @Override + public IntStream onClose(Runnable closeHandler) { + this.closeHandler = closeHandler; + return this; + } + + @Override + public void close() { + delegate.close(); + } + + @Override + public PrimitiveIterator.OfInt iterator() { + return delegate.iterator(); + } + + @Override + public Spliterator.OfInt spliterator() { + return delegate.spliterator(); + } + + @Override + public boolean isParallel() { + return delegate.isParallel(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/spi/LongStreamDecorator.java b/hibernate-core/src/main/java/org/hibernate/query/spi/LongStreamDecorator.java new file mode 100644 index 0000000000..b34c020316 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/spi/LongStreamDecorator.java @@ -0,0 +1,326 @@ +/* + * 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.query.spi; + +import java.util.LongSummaryStatistics; +import java.util.OptionalDouble; +import java.util.OptionalLong; +import java.util.PrimitiveIterator; +import java.util.Spliterator; +import java.util.function.BiConsumer; +import java.util.function.LongBinaryOperator; +import java.util.function.LongConsumer; +import java.util.function.LongFunction; +import java.util.function.LongPredicate; +import java.util.function.LongToDoubleFunction; +import java.util.function.LongToIntFunction; +import java.util.function.LongUnaryOperator; +import java.util.function.ObjLongConsumer; +import java.util.function.Supplier; +import java.util.stream.DoubleStream; +import java.util.stream.IntStream; +import java.util.stream.LongStream; +import java.util.stream.Stream; + +import org.hibernate.Incubating; + +/** + * The {@link LongStreamDecorator} wraps a Java {@link LongStream} and registers a {@code closeHandler} + * which is passed further to any resulting {@link Stream}. + * + * The goal of the {@link LongStreamDecorator} is to close the underlying {@link LongStream} upon + * calling a terminal operation. + * + * @author Vlad Mihalcea + * @since 5.4 + */ +@Incubating +public class LongStreamDecorator implements LongStream { + + private final LongStream delegate; + + private Runnable closeHandler; + + public LongStreamDecorator( + LongStream delegate, + Runnable closeHandler) { + this.delegate = delegate; + this.closeHandler = closeHandler; + this.delegate.onClose( closeHandler ); + } + + @Override + public LongStream filter(LongPredicate predicate) { + return new LongStreamDecorator( + delegate.filter( predicate ), + closeHandler + ); + } + + @Override + public LongStream map(LongUnaryOperator mapper) { + return new LongStreamDecorator( + delegate.map( mapper ), + closeHandler + ); + } + + @Override + public Stream mapToObj(LongFunction mapper) { + return new StreamDecorator<>( + delegate.mapToObj( mapper ), + closeHandler + ); + } + + @Override + public IntStream mapToInt(LongToIntFunction mapper) { + return new IntStreamDecorator( + delegate.mapToInt( mapper ), + closeHandler + ); + } + + @Override + public DoubleStream mapToDouble(LongToDoubleFunction mapper) { + return new DoubleStreamDecorator( + delegate.mapToDouble( mapper ), + closeHandler + ); + } + + @Override + public LongStream flatMap(LongFunction mapper) { + return new LongStreamDecorator( + delegate.flatMap( mapper ), + closeHandler + ); + } + + @Override + public LongStream distinct() { + return new LongStreamDecorator( + delegate.distinct(), + closeHandler + ); + } + + @Override + public LongStream sorted() { + return new LongStreamDecorator( + delegate.sorted(), + closeHandler + ); + } + + @Override + public LongStream peek(LongConsumer action) { + return new LongStreamDecorator( + delegate.peek( action ), + closeHandler + ); + } + + @Override + public LongStream limit(long maxSize) { + return new LongStreamDecorator( + delegate.limit( maxSize ), + closeHandler + ); + } + + @Override + public LongStream skip(long n) { + return new LongStreamDecorator( + delegate.skip( n ), + closeHandler + ); + } + + @Override + public void forEach(LongConsumer action) { + delegate.forEach( action ); + close(); + } + + @Override + public void forEachOrdered(LongConsumer action) { + delegate.forEachOrdered( action ); + close(); + } + + @Override + public long[] toArray() { + long[] result = delegate.toArray(); + close(); + return result; + } + + @Override + public long reduce(long identity, LongBinaryOperator op) { + long result = delegate.reduce( identity, op ); + close(); + return result; + } + + @Override + public OptionalLong reduce(LongBinaryOperator op) { + OptionalLong result = delegate.reduce( op ); + close(); + return result; + } + + @Override + public R collect( + Supplier supplier, ObjLongConsumer accumulator, BiConsumer combiner) { + R result = delegate.collect( supplier, accumulator, combiner ); + close(); + return result; + } + + @Override + public long sum() { + long result = delegate.sum(); + close(); + return result; + } + + @Override + public OptionalLong min() { + OptionalLong result = delegate.min(); + close(); + return result; + } + + @Override + public OptionalLong max() { + OptionalLong result = delegate.max(); + close(); + return result; + } + + @Override + public long count() { + long result = delegate.count(); + close(); + return result; + } + + @Override + public OptionalDouble average() { + OptionalDouble result = delegate.average(); + close(); + return result; + } + + @Override + public LongSummaryStatistics summaryStatistics() { + LongSummaryStatistics result = delegate.summaryStatistics(); + close(); + return result; + } + + @Override + public boolean anyMatch(LongPredicate predicate) { + boolean result = delegate.anyMatch(predicate); + close(); + return result; + } + + @Override + public boolean allMatch(LongPredicate predicate) { + boolean result = delegate.allMatch(predicate); + close(); + return result; + } + + @Override + public boolean noneMatch(LongPredicate predicate) { + boolean result = delegate.noneMatch(predicate); + close(); + return result; + } + + @Override + public OptionalLong findFirst() { + OptionalLong result = delegate.findFirst(); + close(); + return result; + } + + @Override + public OptionalLong findAny() { + OptionalLong result = delegate.findAny(); + close(); + return result; + } + + @Override + public DoubleStream asDoubleStream() { + DoubleStream result = delegate.asDoubleStream(); + close(); + return result; + } + + @Override + public Stream boxed() { + return new StreamDecorator<>( + delegate.boxed(), + closeHandler + ); + } + + @Override + public LongStream sequential() { + return new LongStreamDecorator( + delegate.sequential(), + closeHandler + ); + } + + @Override + public LongStream parallel() { + return new LongStreamDecorator( + delegate.parallel(), + closeHandler + ); + } + + @Override + public LongStream unordered() { + return new LongStreamDecorator( + delegate.unordered(), + closeHandler + ); + } + + @Override + public LongStream onClose(Runnable closeHandler) { + this.closeHandler = closeHandler; + return this; + } + + @Override + public void close() { + delegate.close(); + } + + @Override + public PrimitiveIterator.OfLong iterator() { + return delegate.iterator(); + } + + @Override + public Spliterator.OfLong spliterator() { + return delegate.spliterator(); + } + + @Override + public boolean isParallel() { + return delegate.isParallel(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/spi/StreamDecorator.java b/hibernate-core/src/main/java/org/hibernate/query/spi/StreamDecorator.java new file mode 100644 index 0000000000..526b4ee9b9 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/spi/StreamDecorator.java @@ -0,0 +1,340 @@ +/* + * 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.query.spi; + +import java.lang.reflect.InvocationTargetException; +import java.util.Comparator; +import java.util.Iterator; +import java.util.Optional; +import java.util.Spliterator; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.BinaryOperator; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.IntFunction; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.function.ToDoubleFunction; +import java.util.function.ToIntFunction; +import java.util.function.ToLongFunction; +import java.util.stream.Collector; +import java.util.stream.DoubleStream; +import java.util.stream.IntStream; +import java.util.stream.LongStream; +import java.util.stream.Stream; + +import org.hibernate.HibernateException; +import org.hibernate.Incubating; +import org.hibernate.internal.util.ReflectHelper; + +/** + * The {@link StreamDecorator} wraps a Java {@link Stream} and registers a {@code closeHandler} + * which is passed further to any resulting {@link Stream}. + * + * The goal of the {@link StreamDecorator} is to close the underlying {@link Stream} upon + * calling a terminal operation. + * + * @author Vlad Mihalcea + * @since 5.4 + */ +@Incubating +public class StreamDecorator implements Stream { + + private final Stream delegate; + + private Runnable closeHandler; + + public StreamDecorator( + Stream delegate, + Runnable closeHandler) { + this.delegate = delegate; + this.closeHandler = closeHandler; + this.delegate.onClose( closeHandler ); + } + + @Override + public Stream filter(Predicate predicate) { + return new StreamDecorator( delegate.filter( predicate ), closeHandler ); + } + + @Override + public Stream map(Function mapper) { + return new StreamDecorator<>( delegate.map( mapper ), closeHandler ); + } + + @Override + public IntStream mapToInt(ToIntFunction mapper) { + return new IntStreamDecorator( + delegate.mapToInt( mapper ), + closeHandler + ); + } + + @Override + public LongStream mapToLong(ToLongFunction mapper) { + return new LongStreamDecorator( + delegate.mapToLong( mapper ), + closeHandler + ); + } + + @Override + public DoubleStream mapToDouble(ToDoubleFunction mapper) { + return new DoubleStreamDecorator( + delegate.mapToDouble( mapper ), + closeHandler + ); + } + + @Override + public Stream flatMap(Function> mapper) { + return new StreamDecorator<>( delegate.flatMap( mapper ), closeHandler ); + } + + @Override + public IntStream flatMapToInt(Function mapper) { + return new IntStreamDecorator( + delegate.flatMapToInt( mapper ), + closeHandler + ); + } + + @Override + public LongStream flatMapToLong(Function mapper) { + return new LongStreamDecorator( + delegate.flatMapToLong( mapper ), + closeHandler + ); + } + + @Override + public DoubleStream flatMapToDouble(Function mapper) { + return new DoubleStreamDecorator( + delegate.flatMapToDouble( mapper ), + closeHandler + ); + } + + @Override + public Stream distinct() { + return new StreamDecorator<>( delegate.distinct(), closeHandler ); + } + + @Override + public Stream sorted() { + return new StreamDecorator<>( delegate.sorted(), closeHandler ); + } + + @Override + public Stream sorted(Comparator comparator) { + return new StreamDecorator<>( delegate.sorted( comparator ), closeHandler ); + } + + @Override + public Stream peek(Consumer action) { + return new StreamDecorator<>( delegate.peek( action ), closeHandler ); + } + + @Override + public Stream limit(long maxSize) { + return new StreamDecorator<>( delegate.limit( maxSize ), closeHandler ); + } + + @Override + public Stream skip(long n) { + return new StreamDecorator<>( delegate.skip( n ), closeHandler ); + } + + @Override + public void forEach(Consumer action) { + delegate.forEach( action ); + close(); + } + + @Override + public void forEachOrdered(Consumer action) { + delegate.forEachOrdered( action ); + close(); + } + + @Override + public Object[] toArray() { + Object[] result = delegate.toArray(); + close(); + return result; + } + + @Override + public A[] toArray(IntFunction generator) { + A[] result = delegate.toArray( generator ); + close(); + return result; + } + + @Override + public R reduce(R identity, BinaryOperator accumulator) { + R result = delegate.reduce( identity, accumulator ); + close(); + return result; + } + + @Override + public Optional reduce(BinaryOperator accumulator) { + Optional result = delegate.reduce( accumulator ); + close(); + return result; + } + + @Override + public U reduce( + U identity, BiFunction accumulator, BinaryOperator combiner) { + U result = delegate.reduce( identity, accumulator, combiner ); + close(); + return result; + } + + @Override + public R1 collect( + Supplier supplier, BiConsumer accumulator, BiConsumer combiner) { + R1 result = delegate.collect( supplier, accumulator, combiner ); + close(); + return result; + } + + @Override + public R1 collect(Collector collector) { + R1 result = delegate.collect( collector ); + close(); + return result; + } + + @Override + public Optional min(Comparator comparator) { + Optional result = delegate.min( comparator ); + close(); + return result; + } + + @Override + public Optional max(Comparator comparator) { + Optional result = delegate.max( comparator ); + close(); + return result; + } + + @Override + public long count() { + long result = delegate.count(); + close(); + return result; + } + + @Override + public boolean anyMatch(Predicate predicate) { + boolean result = delegate.anyMatch( predicate ); + close(); + return result; + } + + @Override + public boolean allMatch(Predicate predicate) { + boolean result = delegate.allMatch( predicate ); + close(); + return result; + } + + @Override + public boolean noneMatch(Predicate predicate) { + boolean result = delegate.noneMatch( predicate ); + close(); + return result; + } + + @Override + public Optional findFirst() { + Optional result = delegate.findFirst(); + close(); + return result; + } + + @Override + public Optional findAny() { + Optional result = delegate.findAny(); + close(); + return result; + } + + @Override + public Iterator iterator() { + return delegate.iterator(); + } + + @Override + public Spliterator spliterator() { + return delegate.spliterator(); + } + + @Override + public boolean isParallel() { + return delegate.isParallel(); + } + + @Override + public Stream sequential() { + return new StreamDecorator<>( delegate.sequential(), closeHandler ); + } + + @Override + public Stream parallel() { + return new StreamDecorator<>( delegate.parallel(), closeHandler ); + } + + @Override + public Stream unordered() { + return new StreamDecorator<>( delegate.unordered(), closeHandler ); + } + + @Override + public Stream onClose(Runnable closeHandler) { + this.closeHandler = closeHandler; + return this; + } + + @Override + public void close() { + delegate.close(); + } + + //Methods added to JDK 9 + + public Stream takeWhile(Predicate predicate) { + try { + @SuppressWarnings("unchecked") + Stream result = (Stream) + ReflectHelper.getMethod( Stream.class, "takeWhile", Predicate.class ) + .invoke( delegate, predicate ); + return new StreamDecorator<>( result, closeHandler ); + } + catch (IllegalAccessException | InvocationTargetException e) { + throw new HibernateException( e ); + } + } + + public Stream dropWhile(Predicate predicate) { + try { + @SuppressWarnings("unchecked") + Stream result = (Stream) + ReflectHelper.getMethod( Stream.class, "dropWhile", Predicate.class ) + .invoke( delegate, predicate ); + return new StreamDecorator<>( result, closeHandler ); + } + catch (IllegalAccessException | InvocationTargetException e) { + throw new HibernateException( e ); + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/stream/basic/BasicStreamTest.java b/hibernate-core/src/test/java/org/hibernate/test/stream/basic/BasicStreamTest.java index 94df2adaf0..471a193899 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/stream/basic/BasicStreamTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/stream/basic/BasicStreamTest.java @@ -49,7 +49,7 @@ public class BasicStreamTest extends BaseNonConfigCoreFunctionalTestCase { final Stream stream = session.createQuery( "from MyEntity", MyEntity.class ).stream(); assertThat( ( (SessionImplementor) session ).getJdbcCoordinator().getLogicalConnection().getResourceRegistry().hasRegisteredResources(), is( true ) ); stream.forEach( System.out::println ); - assertThat( ( (SessionImplementor) session ).getJdbcCoordinator().getLogicalConnection().getResourceRegistry().hasRegisteredResources(), is( true ) ); + assertThat( ( (SessionImplementor) session ).getJdbcCoordinator().getLogicalConnection().getResourceRegistry().hasRegisteredResources(), is( false ) ); stream.close(); assertThat( ( (SessionImplementor) session ).getJdbcCoordinator().getLogicalConnection().getResourceRegistry().hasRegisteredResources(), is( false ) ); diff --git a/hibernate-core/src/test/java/org/hibernate/test/stream/basic/JpaStreamTest.java b/hibernate-core/src/test/java/org/hibernate/test/stream/basic/JpaStreamTest.java index 0c23546988..14fefac446 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/stream/basic/JpaStreamTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/stream/basic/JpaStreamTest.java @@ -6,27 +6,34 @@ */ package org.hibernate.test.stream.basic; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Collectors; import java.util.stream.Stream; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; -import javax.persistence.Tuple; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Root; import org.hibernate.Session; -import org.hibernate.boot.MetadataSources; -import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.engine.jdbc.spi.JdbcCoordinator; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.internal.util.ReflectHelper; +import org.hibernate.resource.jdbc.ResourceRegistry; +import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; import org.junit.Test; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.core.Is.is; import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping; import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; /** * @author Steve Ebersole @@ -70,12 +77,180 @@ public class JpaStreamTest extends BaseNonConfigCoreFunctionalTestCase { } ); } + @Test + @TestForIssue( jiraKey = "HHH-13872") + @RequiresDialect(H2Dialect.class) + public void testStreamCloseOnTerminalOperation() { + doInHibernate( this::sessionFactory, session -> { + session.createQuery( "delete from MyEntity" ).executeUpdate(); + + for ( int i = 1; i <= 10; i++ ) { + MyEntity e = new MyEntity(); + e.id = i; + e.name = "Test"; + session.persist( e ); + } + } ); + + doInHibernate( this::sessionFactory, session -> { + Stream stream = session + .createQuery( "SELECT me FROM MyEntity me" ) + .getResultStream(); + + ResourceRegistry resourceRegistry = resourceRegistry(session); + assertTrue( resourceRegistry.hasRegisteredResources() ); + + List entities = stream.collect( Collectors.toList() ) ; + assertEquals(10, entities.size()); + + assertFalse( resourceRegistry.hasRegisteredResources() ); + } ); + + doInHibernate( this::sessionFactory, session -> { + Stream stream = session + .createQuery( "SELECT me FROM MyEntity me" ) + .getResultStream(); + + ResourceRegistry resourceRegistry = resourceRegistry(session); + assertTrue( resourceRegistry.hasRegisteredResources() ); + + int sum = stream.mapToInt( MyEntity::getId ).sum(); + assertEquals(55, sum); + + assertFalse( resourceRegistry.hasRegisteredResources() ); + } ); + + doInHibernate( this::sessionFactory, session -> { + Stream stream = session + .createQuery( "SELECT me FROM MyEntity me" ) + .getResultStream(); + + ResourceRegistry resourceRegistry = resourceRegistry(session); + assertTrue( resourceRegistry.hasRegisteredResources() ); + + long result = stream.mapToLong( entity -> entity.id * 10 ).min().getAsLong(); + assertEquals(10, result); + + assertFalse( resourceRegistry.hasRegisteredResources() ); + } ); + + doInHibernate( this::sessionFactory, session -> { + Stream stream = session + .createQuery( "SELECT me FROM MyEntity me" ) + .getResultStream(); + + ResourceRegistry resourceRegistry = resourceRegistry(session); + assertTrue( resourceRegistry.hasRegisteredResources() ); + + double result = stream.mapToDouble( entity -> entity.id * 0.1D ).max().getAsDouble(); + assertEquals(1, result, 0.1); + + assertFalse( resourceRegistry.hasRegisteredResources() ); + } ); + + //Test call close explicitly + doInHibernate( this::sessionFactory, session -> { + + try (Stream stream = session + .createQuery( "SELECT me.id FROM MyEntity me" ) + .getResultStream()) { + + ResourceRegistry resourceRegistry = resourceRegistry( session ); + assertTrue( resourceRegistry.hasRegisteredResources() ); + + Object[] result = stream.sorted().skip( 5 ).limit( 5 ).toArray(); + assertEquals( 5, result.length ); + assertEquals( 6, result[0] ); + assertEquals( 10, result[4] ); + + assertFalse( resourceRegistry.hasRegisteredResources() ); + } + } ); + + //Test Java 9 Stream methods + doInHibernate( this::sessionFactory, session -> { + Method takeWhileMethod = ReflectHelper.getMethod( Stream.class, "takeWhile", Predicate.class ); + + if ( takeWhileMethod != null ) { + try (Stream stream = session + .createQuery( "SELECT me.id FROM MyEntity me" ) + .getResultStream()) { + + ResourceRegistry resourceRegistry = resourceRegistry( session ); + assertTrue( resourceRegistry.hasRegisteredResources() ); + + Predicate predicate = id -> id <= 5; + + Stream takeWhileStream = (Stream) takeWhileMethod.invoke( stream, predicate ); + + List result = takeWhileStream.collect( Collectors.toList() ); + + assertEquals( 5, result.size() ); + assertTrue( result.contains( 1 ) ); + assertTrue( result.contains( 3 ) ); + assertTrue( result.contains( 5 ) ); + + assertFalse( resourceRegistry.hasRegisteredResources() ); + } + catch (IllegalAccessException | InvocationTargetException e) { + fail( "Could not execute takeWhile because of " + e.getMessage() ); + } + } + } ); + + doInHibernate( this::sessionFactory, session -> { + Method dropWhileMethod = ReflectHelper.getMethod( Stream.class, "dropWhile", Predicate.class ); + + if ( dropWhileMethod != null ) { + try (Stream stream = session + .createQuery( "SELECT me.id FROM MyEntity me" ) + .getResultStream()) { + + ResourceRegistry resourceRegistry = resourceRegistry( session ); + assertTrue( resourceRegistry.hasRegisteredResources() ); + + Predicate predicate = id -> id <= 5; + + Stream dropWhileStream = (Stream) dropWhileMethod.invoke( stream, predicate ); + + List result = dropWhileStream.collect( Collectors.toList() ); + + assertEquals( 5, result.size() ); + assertTrue( result.contains( 6 ) ); + assertTrue( result.contains( 8 ) ); + assertTrue( result.contains( 10 ) ); + + assertFalse( resourceRegistry.hasRegisteredResources() ); + } + catch (IllegalAccessException | InvocationTargetException e) { + fail( "Could not execute takeWhile because of " + e.getMessage() ); + } + } + } ); + } + + private ResourceRegistry resourceRegistry(Session session) { + SharedSessionContractImplementor sharedSessionContractImplementor = (SharedSessionContractImplementor) session; + JdbcCoordinator jdbcCoordinator = sharedSessionContractImplementor.getJdbcCoordinator(); + return jdbcCoordinator.getLogicalConnection().getResourceRegistry(); + } + @Entity(name = "MyEntity") @Table(name="MyEntity") public static class MyEntity { + @Id public Integer id; + public String name; + + public Integer getId() { + return id; + } + + public String getName() { + return name; + } } } From e9df1cb62611022e4f0873e8488e930161ba14c1 Mon Sep 17 00:00:00 2001 From: Nathan Xu Date: Tue, 18 Feb 2020 14:09:33 -0500 Subject: [PATCH 09/27] HHH-13853 fix minor issue --- .../jpa/boot/internal/EntityManagerFactoryBuilderImpl.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/boot/internal/EntityManagerFactoryBuilderImpl.java b/hibernate-core/src/main/java/org/hibernate/jpa/boot/internal/EntityManagerFactoryBuilderImpl.java index f83c4d56a3..4aa26f7a01 100644 --- a/hibernate-core/src/main/java/org/hibernate/jpa/boot/internal/EntityManagerFactoryBuilderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/boot/internal/EntityManagerFactoryBuilderImpl.java @@ -181,7 +181,7 @@ public class EntityManagerFactoryBuilderImpl implements EntityManagerFactoryBuil ClassLoaderService providedClassLoaderService ) { this( persistenceUnit, integrationSettings, null, providedClassLoaderService); } - + private EntityManagerFactoryBuilderImpl( PersistenceUnitDescriptor persistenceUnit, Map integrationSettings, @@ -193,13 +193,14 @@ public class EntityManagerFactoryBuilderImpl implements EntityManagerFactoryBuil this.persistenceUnit = persistenceUnit; if ( integrationSettings == null ) { - integrationSettings = Collections.emptyMap(); + integrationSettings = new HashMap(); } Map mergedIntegrationSettings = null; Properties properties = persistenceUnit.getProperties(); if ( properties != null ) { - mergedIntegrationSettings = new HashMap( persistenceUnit.getProperties() ); + // original integratin setting entries take precedence + mergedIntegrationSettings = new HashMap( properties ); mergedIntegrationSettings.putAll( integrationSettings ); } From c4bd5937e3d6e83c5c65582123d3ba989a9cc47f Mon Sep 17 00:00:00 2001 From: The Geeky Asian Date: Thu, 20 Feb 2020 21:04:28 +0400 Subject: [PATCH 10/27] HHH-13874 - Deprecating methods that will be removed soon Two methods that are dropped in v6.0 are now marked as deprecated in this commit. As discussed in the removal PR https://github.com/hibernate/hibernate-orm/pull/3229 --- .../src/main/java/org/hibernate/mapping/Constraint.java | 1 + .../org/hibernate/testing/junit4/BaseCoreFunctionalTestCase.java | 1 + 2 files changed, 2 insertions(+) diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Constraint.java b/hibernate-core/src/main/java/org/hibernate/mapping/Constraint.java index f1f32981ac..76a4f589c1 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Constraint.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Constraint.java @@ -156,6 +156,7 @@ public abstract class Constraint implements RelationalModel, Exportable, Seriali return columns.iterator(); } + @Deprecated public Iterator columnIterator() { return columns.iterator(); } diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit4/BaseCoreFunctionalTestCase.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit4/BaseCoreFunctionalTestCase.java index 3c231652b1..09a26044aa 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/junit4/BaseCoreFunctionalTestCase.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit4/BaseCoreFunctionalTestCase.java @@ -236,6 +236,7 @@ public abstract class BaseCoreFunctionalTestCase extends BaseUnitTestCase { return NO_MAPPINGS; } + @Deprecated protected String[] getXmlFiles() { // todo : rename to getOrmXmlFiles() return NO_MAPPINGS; From e98e89d80627a70f2a9810e3a4dd88d29e21cc8c Mon Sep 17 00:00:00 2001 From: The Geeky Asian Date: Thu, 20 Feb 2020 23:44:55 +0400 Subject: [PATCH 11/27] HHH-13874 - Messages added for the deprecated methods. --- .../src/main/java/org/hibernate/mapping/Constraint.java | 8 ++++++++ .../testing/junit4/BaseCoreFunctionalTestCase.java | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Constraint.java b/hibernate-core/src/main/java/org/hibernate/mapping/Constraint.java index 76a4f589c1..d108249613 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Constraint.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Constraint.java @@ -156,6 +156,14 @@ public abstract class Constraint implements RelationalModel, Exportable, Seriali return columns.iterator(); } + /** + * This is a duplicate method, that has been removed in v.6.0. + * {@link #getColumnIterator()} is the one that will stay. + * + * @author TheGeekyAsian + * + * @deprecated (Since 6.0) use {@link #getColumnIterator()} instead. + */ @Deprecated public Iterator columnIterator() { return columns.iterator(); diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit4/BaseCoreFunctionalTestCase.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit4/BaseCoreFunctionalTestCase.java index 09a26044aa..23e69f9ced 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/junit4/BaseCoreFunctionalTestCase.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit4/BaseCoreFunctionalTestCase.java @@ -236,6 +236,12 @@ public abstract class BaseCoreFunctionalTestCase extends BaseUnitTestCase { return NO_MAPPINGS; } + /** + * + * @author TheGeekyAsian + * + * @deprecated (Since 6.0) this method will be renamed to getOrmXmlFile(). + */ @Deprecated protected String[] getXmlFiles() { // todo : rename to getOrmXmlFiles() From b914b02dcaf48dde8c9bf5e4b965056c194477e8 Mon Sep 17 00:00:00 2001 From: The Geeky Asian Date: Fri, 21 Feb 2020 10:49:51 +0400 Subject: [PATCH 12/27] HHH-13874 - Removing the @author added. The newly added @author in the previous commit or deprecation messages has been removed in this commit. --- .../src/main/java/org/hibernate/mapping/Constraint.java | 2 -- .../hibernate/testing/junit4/BaseCoreFunctionalTestCase.java | 2 -- 2 files changed, 4 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Constraint.java b/hibernate-core/src/main/java/org/hibernate/mapping/Constraint.java index d108249613..344ea32335 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Constraint.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Constraint.java @@ -160,8 +160,6 @@ public abstract class Constraint implements RelationalModel, Exportable, Seriali * This is a duplicate method, that has been removed in v.6.0. * {@link #getColumnIterator()} is the one that will stay. * - * @author TheGeekyAsian - * * @deprecated (Since 6.0) use {@link #getColumnIterator()} instead. */ @Deprecated diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit4/BaseCoreFunctionalTestCase.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit4/BaseCoreFunctionalTestCase.java index 23e69f9ced..8b4526b128 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/junit4/BaseCoreFunctionalTestCase.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit4/BaseCoreFunctionalTestCase.java @@ -237,8 +237,6 @@ public abstract class BaseCoreFunctionalTestCase extends BaseUnitTestCase { } /** - * - * @author TheGeekyAsian * * @deprecated (Since 6.0) this method will be renamed to getOrmXmlFile(). */ From cab651e19451230ce61c25bb5f68b15a654d0a8f Mon Sep 17 00:00:00 2001 From: Daniel Shuy Date: Mon, 24 Feb 2020 21:42:48 +0800 Subject: [PATCH 13/27] HHH-13799 : Criteria API support for Hibernate Spatial (#3159) * HHH-13799 : Criteria API support for Hibernate Spatial Co-authored-by: Karel Maesen --- .../db2/resources/hibernate.properties | 2 +- .../resources/hibernate.properties | 2 +- hibernate-spatial/matrix-test.sh | 3 + .../hibernate/spatial/SpatialFunction.java | 10 +- .../spatial/dialect/WithCustomJPAFilter.java | 18 + .../spatial/dialect/h2geodb/GeoDBDialect.java | 31 + .../dialect/hana/HANASpatialDialect.java | 19 + .../dialect/mysql/MySQL5SpatialFunctions.java | 8 + .../dialect/mysql/MySQL8SpatialFunctions.java | 8 + .../dialect/oracle/OracleSDOSupport.java | 12 +- .../oracle/OracleSpatial10gDialect.java | 8 +- .../oracle/OracleSpatialFunctions.java | 7 + .../oracle/OracleSpatialSDO10gDialect.java | 9 +- .../dialect/postgis/PostgisFunctions.java | 31 + .../dialect/sqlserver/SqlServerFunctions.java | 3 + .../spatial/predicate/FilterPredicate.java | 80 +++ .../spatial/predicate/SpatialPredicates.java | 575 ++++++++++++++++++ .../integration/TestSpatialPredicates.java | 264 ++++++++ .../spatial/testing/TestSupportFactories.java | 1 - .../dialects/postgis/PostgisTestSupport.java | 9 +- 20 files changed, 1087 insertions(+), 13 deletions(-) create mode 100644 hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/WithCustomJPAFilter.java create mode 100644 hibernate-spatial/src/main/java/org/hibernate/spatial/predicate/FilterPredicate.java create mode 100644 hibernate-spatial/src/main/java/org/hibernate/spatial/predicate/SpatialPredicates.java create mode 100644 hibernate-spatial/src/test/java/org/hibernate/spatial/integration/TestSpatialPredicates.java diff --git a/hibernate-spatial/databases/db2/resources/hibernate.properties b/hibernate-spatial/databases/db2/resources/hibernate.properties index dd6ec8b7a7..8e18cb62c5 100644 --- a/hibernate-spatial/databases/db2/resources/hibernate.properties +++ b/hibernate-spatial/databases/db2/resources/hibernate.properties @@ -10,7 +10,7 @@ hibernate.dialect org.hibernate.spatial.dialect.db2.DB2SpatialDialect hibernate.connection.driver_class com.ibm.db2.jcc.DB2Driver hibernate.connection.url jdbc:db2://localhost:50000/hibern8 hibernate.connection.username db2inst1 -hibernate.connection.password password +hibernate.connection.password oPucroAsMAgL hibernate.connection.pool_size 5 diff --git a/hibernate-spatial/databases/sqlserver2012_spatial/resources/hibernate.properties b/hibernate-spatial/databases/sqlserver2012_spatial/resources/hibernate.properties index 741dbcb220..85e9226d79 100644 --- a/hibernate-spatial/databases/sqlserver2012_spatial/resources/hibernate.properties +++ b/hibernate-spatial/databases/sqlserver2012_spatial/resources/hibernate.properties @@ -9,7 +9,7 @@ hibernate.dialect org.hibernate.spatial.dialect.sqlserver.SqlServer2012SpatialDi hibernate.connection.driver_class com.microsoft.sqlserver.jdbc.SQLServerDriver hibernate.connection.url jdbc:sqlserver://localhost:1433;databaseName=TestDb hibernate.connection.username hibern8 -hibernate.connection.password hibern8Pass +hibernate.connection.password langpaswoord123A%1 hibernate.connection.pool_size 5 diff --git a/hibernate-spatial/matrix-test.sh b/hibernate-spatial/matrix-test.sh index 911098fb54..472055c484 100755 --- a/hibernate-spatial/matrix-test.sh +++ b/hibernate-spatial/matrix-test.sh @@ -1,5 +1,8 @@ #! /bin/bash +## The same effect can be achieved by setting the system properties +# in ~/.gradle/gradle.properties + TASK=matrix if [[ -n $@ ]]; then TASK="$@" diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/SpatialFunction.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/SpatialFunction.java index 4d57e565be..4227b04413 100644 --- a/hibernate-spatial/src/main/java/org/hibernate/spatial/SpatialFunction.java +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/SpatialFunction.java @@ -164,8 +164,16 @@ public enum SpatialFunction { /** * the extents function */ - extent( "common" ); + extent( "common" ), + /** + * The filter function + *

+ *

Corresponds to the Oracle Spatial's "SDO_FILTER" function, or the "&&" operator of PostGIS. + */ + filter( "filter" ), + + ; private final String description; diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/WithCustomJPAFilter.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/WithCustomJPAFilter.java new file mode 100644 index 0000000000..2d81d15d23 --- /dev/null +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/WithCustomJPAFilter.java @@ -0,0 +1,18 @@ +/* + * 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.spatial.dialect; + +/** + * An Interface for {@code SpatialDialect}s that require a custom + * rendering to JPAQL for the filter predicate + *

+ * Created by Karel Maesen, Geovise BVBA on 09/02/2020. + */ +public interface WithCustomJPAFilter { + + String filterExpression(String geometryParam, String filterParam); +} diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/h2geodb/GeoDBDialect.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/h2geodb/GeoDBDialect.java index 38ef54b974..4c4df78cad 100644 --- a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/h2geodb/GeoDBDialect.java +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/h2geodb/GeoDBDialect.java @@ -7,9 +7,15 @@ package org.hibernate.spatial.dialect.h2geodb; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.hibernate.QueryException; import org.hibernate.boot.model.TypeContributions; import org.hibernate.dialect.H2Dialect; import org.hibernate.dialect.function.StandardSQLFunction; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.service.ServiceRegistry; import org.hibernate.spatial.GeolatteGeometryJavaTypeDescriptor; import org.hibernate.spatial.GeolatteGeometryType; @@ -19,6 +25,7 @@ import org.hibernate.spatial.SpatialDialect; import org.hibernate.spatial.SpatialFunction; import org.hibernate.spatial.SpatialRelation; import org.hibernate.type.StandardBasicTypes; +import org.hibernate.type.Type; /** * Extends the H2Dialect by also including information on spatial functions. @@ -74,6 +81,8 @@ public class GeoDBDialect extends H2Dialect implements SpatialDialect { registerFunction( "dwithin", new StandardSQLFunction( "ST_DWithin", StandardBasicTypes.BOOLEAN ) ); + // Register Spatial Filter function + registerFunction( SpatialFunction.filter.name(), new FilterFunction() ); } @Override @@ -157,4 +166,26 @@ public class GeoDBDialect extends H2Dialect implements SpatialDialect { return function != SpatialFunction.difference && ( getFunctions().get( function.toString() ) != null ); } + private static class FilterFunction extends StandardSQLFunction { + + public FilterFunction() { + super( "&&" ); + } + + @Override + public String render( + Type firstArgumentType, List arguments, SessionFactoryImplementor sessionFactory) { + int argumentCount = arguments.size(); + if ( argumentCount != 2 ) { + throw new QueryException( String.format( "2 arguments expected, received %d", argumentCount ) ); + } + + return Stream.of( + String.valueOf( arguments.get( 0 ) ), + getRenderedName( arguments ), + String.valueOf( arguments.get( 1 ) ) + ).collect( Collectors.joining( " ", "(", ")" ) ); + } + } + } diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/hana/HANASpatialDialect.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/hana/HANASpatialDialect.java index 9a41fe09ca..43b3fa0ac7 100644 --- a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/hana/HANASpatialDialect.java +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/hana/HANASpatialDialect.java @@ -7,11 +7,13 @@ package org.hibernate.spatial.dialect.hana; import java.sql.Types; +import java.util.List; import org.hibernate.boot.model.TypeContributions; import org.hibernate.dialect.HANAColumnStoreDialect; import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.config.spi.ConfigurationService.Converter; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.service.ServiceRegistry; import org.hibernate.spatial.GeolatteGeometryJavaTypeDescriptor; import org.hibernate.spatial.GeolatteGeometryType; @@ -22,6 +24,7 @@ import org.hibernate.spatial.SpatialDialect; import org.hibernate.spatial.SpatialFunction; import org.hibernate.spatial.SpatialRelation; import org.hibernate.type.StandardBasicTypes; +import org.hibernate.type.Type; public class HANASpatialDialect extends HANAColumnStoreDialect implements SpatialDialect { @@ -102,6 +105,9 @@ public class HANASpatialDialect extends HANAColumnStoreDialect implements Spatia registerFunction( SpatialFunction.within.name(), new HANASpatialFunction( "ST_Within", StandardBasicTypes.NUMERIC_BOOLEAN, true ) ); + registerFunction( + SpatialFunction.filter.name(), + new FilterFunction() ); /* * Additional HANA functions @@ -408,4 +414,17 @@ public class HANASpatialDialect extends HANAColumnStoreDialect implements Spatia } return false; } + + private static class FilterFunction extends HANASpatialFunction { + + public FilterFunction() { + super( "ST_IntersectsFilter", StandardBasicTypes.NUMERIC_BOOLEAN, true ); + } + + @Override + public String render( + Type firstArgumentType, List arguments, SessionFactoryImplementor sessionFactory) { + return super.render( firstArgumentType, arguments, sessionFactory ) + " = 1"; + } + } } diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/mysql/MySQL5SpatialFunctions.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/mysql/MySQL5SpatialFunctions.java index 40bd5fbf98..85f972129b 100644 --- a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/mysql/MySQL5SpatialFunctions.java +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/mysql/MySQL5SpatialFunctions.java @@ -7,6 +7,7 @@ package org.hibernate.spatial.dialect.mysql; import org.hibernate.dialect.function.StandardSQLFunction; +import org.hibernate.spatial.SpatialFunction; import org.hibernate.spatial.dialect.SpatialFunctionsRegistry; import org.hibernate.type.StandardBasicTypes; @@ -163,6 +164,13 @@ class MySQL5SpatialFunctions extends SpatialFunctionsRegistry { // "union" // ) // ); + + functionMap.put( + SpatialFunction.filter.name(), new StandardSQLFunction( + "MBRIntersects", + StandardBasicTypes.BOOLEAN + ) + ); } } diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/mysql/MySQL8SpatialFunctions.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/mysql/MySQL8SpatialFunctions.java index 4084e99b17..a0ec9c314f 100644 --- a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/mysql/MySQL8SpatialFunctions.java +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/mysql/MySQL8SpatialFunctions.java @@ -7,6 +7,7 @@ package org.hibernate.spatial.dialect.mysql; import org.hibernate.dialect.function.StandardSQLFunction; +import org.hibernate.spatial.SpatialFunction; import org.hibernate.spatial.dialect.SpatialFunctionsRegistry; import org.hibernate.type.StandardBasicTypes; @@ -169,6 +170,13 @@ class MySQL8SpatialFunctions extends SpatialFunctionsRegistry { "ST_Union" ) ); + + functionMap.put( + SpatialFunction.filter.name(), new StandardSQLFunction( + "MBRIntersects", + StandardBasicTypes.BOOLEAN + ) + ); } } diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/oracle/OracleSDOSupport.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/oracle/OracleSDOSupport.java index e7af3ce58d..2b066d561b 100644 --- a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/oracle/OracleSDOSupport.java +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/oracle/OracleSDOSupport.java @@ -7,6 +7,7 @@ package org.hibernate.spatial.dialect.oracle; import java.io.Serializable; +import java.util.Locale; import org.hibernate.boot.model.TypeContributions; import org.hibernate.boot.registry.selector.spi.StrategySelector; @@ -22,6 +23,7 @@ import org.hibernate.spatial.SpatialDialect; import org.hibernate.spatial.SpatialFunction; import org.hibernate.spatial.SpatialRelation; import org.hibernate.spatial.dialect.SpatialFunctionsRegistry; +import org.hibernate.spatial.dialect.WithCustomJPAFilter; import org.jboss.logging.Logger; @@ -33,7 +35,7 @@ import org.geolatte.geom.codec.db.oracle.OracleJDBCTypeFactory; *

* Created by Karel Maesen, Geovise BVBA on 01/11/16. */ -class OracleSDOSupport implements SpatialDialect, Serializable { +class OracleSDOSupport implements SpatialDialect, Serializable, WithCustomJPAFilter { private static final HSMessageLogger log = Logger.getMessageLogger( HSMessageLogger.class, @@ -290,7 +292,7 @@ class OracleSDOSupport implements SpatialDialect, Serializable { */ @Override public String getHavingSridSQL(String columnName) { - return String.format( " (MDSYS.ST_GEOMETRY(%s).ST_SRID() = ?)", columnName ); + return String.format( " (MDSYS.ST_GEOMETRY(%s).ST_SRID() = ?)", columnName , Locale.US); } /** @@ -304,7 +306,7 @@ class OracleSDOSupport implements SpatialDialect, Serializable { */ @Override public String getIsEmptySQL(String columnName, boolean isEmpty) { - return String.format( "( MDSYS.ST_GEOMETRY(%s).ST_ISEMPTY() = %d )", columnName, isEmpty ? 1 : 0 ); + return String.format( "( MDSYS.ST_GEOMETRY(%s).ST_ISEMPTY() = %d )", columnName, isEmpty ? 1 : 0 , Locale.US); } /** @@ -331,4 +333,8 @@ class OracleSDOSupport implements SpatialDialect, Serializable { } + @Override + public String filterExpression(String geometryParam, String filterParam) { + return SpatialFunction.filter.name() + "(" + geometryParam + ", " + filterParam + ") = 'TRUE' "; + } } diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/oracle/OracleSpatial10gDialect.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/oracle/OracleSpatial10gDialect.java index ec6de6ddc2..84ce98d45d 100644 --- a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/oracle/OracleSpatial10gDialect.java +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/oracle/OracleSpatial10gDialect.java @@ -18,6 +18,7 @@ import org.hibernate.service.ServiceRegistry; import org.hibernate.spatial.HSMessageLogger; import org.hibernate.spatial.SpatialDialect; import org.hibernate.spatial.SpatialFunction; +import org.hibernate.spatial.dialect.WithCustomJPAFilter; import org.jboss.logging.Logger; @@ -26,7 +27,7 @@ import org.jboss.logging.Logger; * * @author Karel Maesen */ -public class OracleSpatial10gDialect extends Oracle10gDialect implements SpatialDialect, Serializable { +public class OracleSpatial10gDialect extends Oracle10gDialect implements SpatialDialect, WithCustomJPAFilter, Serializable { private static final HSMessageLogger log = Logger.getMessageLogger( HSMessageLogger.class, @@ -101,5 +102,8 @@ public class OracleSpatial10gDialect extends Oracle10gDialect implements Spatial return ( getFunctions().get( function.toString() ) != null ); } - + @Override + public String filterExpression(String geometryParam, String filterParam) { + return sdoSupport.filterExpression( geometryParam, filterParam ); + } } diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/oracle/OracleSpatialFunctions.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/oracle/OracleSpatialFunctions.java index a93429c66e..e25e5e5e87 100644 --- a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/oracle/OracleSpatialFunctions.java +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/oracle/OracleSpatialFunctions.java @@ -12,6 +12,7 @@ import org.hibernate.QueryException; import org.hibernate.dialect.function.StandardSQLFunction; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.spatial.SpatialAnalysis; +import org.hibernate.spatial.SpatialFunction; import org.hibernate.spatial.SpatialRelation; import org.hibernate.spatial.dialect.SpatialFunctionsRegistry; import org.hibernate.spatial.dialect.oracle.criterion.OracleSpatialAggregate; @@ -138,6 +139,12 @@ class OracleSpatialFunctions extends SpatialFunctionsRegistry { new SpatialAggregationFunction( "extent", OracleSpatialAggregate.EXTENT, sdoSupport ) ); + // spatial filter function + put( + SpatialFunction.filter.name(), + new StandardSQLFunction( "SDO_FILTER" ) + ); + //other common functions put( "transform", new StandardSQLFunction( "SDO_CS.TRANSFORM" ) ); diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/oracle/OracleSpatialSDO10gDialect.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/oracle/OracleSpatialSDO10gDialect.java index fc97d85b33..297ae591a2 100644 --- a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/oracle/OracleSpatialSDO10gDialect.java +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/oracle/OracleSpatialSDO10gDialect.java @@ -17,6 +17,7 @@ import org.hibernate.service.ServiceRegistry; import org.hibernate.spatial.HSMessageLogger; import org.hibernate.spatial.SpatialDialect; import org.hibernate.spatial.SpatialFunction; +import org.hibernate.spatial.dialect.WithCustomJPAFilter; import org.jboss.logging.Logger; @@ -25,7 +26,8 @@ import org.jboss.logging.Logger; *

* Created by Karel Maesen, Geovise BVBA on 11/02/17. */ -public class OracleSpatialSDO10gDialect extends Oracle10gDialect implements SpatialDialect, Serializable { +public class OracleSpatialSDO10gDialect extends Oracle10gDialect + implements SpatialDialect, WithCustomJPAFilter, Serializable { private static final HSMessageLogger log = Logger.getMessageLogger( HSMessageLogger.class, @@ -100,5 +102,8 @@ public class OracleSpatialSDO10gDialect extends Oracle10gDialect implements Spat return !function.equals( SpatialFunction.crosses ) && ( getFunctions().get( function.toString() ) != null ); } - + @Override + public String filterExpression(String geometryParam, String filterParam) { + return sdoSupport.filterExpression( geometryParam, filterParam ); + } } diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisFunctions.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisFunctions.java index 2a97c77cf8..0c3c08eb19 100644 --- a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisFunctions.java +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisFunctions.java @@ -7,9 +7,13 @@ package org.hibernate.spatial.dialect.postgis; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.hibernate.QueryException; import org.hibernate.dialect.function.StandardSQLFunction; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.spatial.SpatialFunction; import org.hibernate.spatial.dialect.SpatialFunctionsRegistry; import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.Type; @@ -177,6 +181,11 @@ class PostgisFunctions extends SpatialFunctionsRegistry { "extent", new ExtentFunction() ); + //register Spatial Filter function + put( + SpatialFunction.filter.name(), new FilterFunction() + ); + //other common functions put( "dwithin", new StandardSQLFunction( @@ -206,4 +215,26 @@ class PostgisFunctions extends SpatialFunctionsRegistry { } } + private static class FilterFunction extends StandardSQLFunction { + + public FilterFunction() { + super( "&&" ); + } + + @Override + public String render( + Type firstArgumentType, List arguments, SessionFactoryImplementor sessionFactory) { + int argumentCount = arguments.size(); + if ( argumentCount != 2 ) { + throw new QueryException( String.format( "2 arguments expected, received %d", argumentCount ) ); + } + + return Stream.of( + String.valueOf( arguments.get( 0 ) ), + getRenderedName( arguments ), + String.valueOf( arguments.get( 1 ) ) + ).collect( Collectors.joining( " ", "(", ")" ) ); + } + } + } diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/sqlserver/SqlServerFunctions.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/sqlserver/SqlServerFunctions.java index d002221f17..5dd20c9b28 100644 --- a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/sqlserver/SqlServerFunctions.java +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/sqlserver/SqlServerFunctions.java @@ -7,6 +7,7 @@ package org.hibernate.spatial.dialect.sqlserver; import org.hibernate.dialect.function.SQLFunctionTemplate; +import org.hibernate.spatial.SpatialFunction; import org.hibernate.spatial.dialect.SpatialFunctionsRegistry; import org.hibernate.type.StandardBasicTypes; @@ -62,5 +63,7 @@ class SqlServerFunctions extends SpatialFunctionsRegistry { "pointonsurface", new SqlServerMethod( "STPointOnSurface" ) ); + // Register spatial filter function. + put( SpatialFunction.filter.name(), new SQLFunctionTemplate( StandardBasicTypes.BOOLEAN, "?1.Filter(?2)" ) ); } } diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/predicate/FilterPredicate.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/predicate/FilterPredicate.java new file mode 100644 index 0000000000..88883793b3 --- /dev/null +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/predicate/FilterPredicate.java @@ -0,0 +1,80 @@ +/* + * 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.spatial.predicate; + +import java.io.Serializable; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.Expression; +import javax.persistence.criteria.Predicate; + +import org.hibernate.dialect.Dialect; +import org.hibernate.query.criteria.internal.CriteriaBuilderImpl; +import org.hibernate.query.criteria.internal.ParameterRegistry; +import org.hibernate.query.criteria.internal.Renderable; +import org.hibernate.query.criteria.internal.compile.RenderingContext; +import org.hibernate.query.criteria.internal.predicate.AbstractSimplePredicate; +import org.hibernate.spatial.SpatialDialect; +import org.hibernate.spatial.SpatialFunction; +import org.hibernate.spatial.criterion.SpatialFilter; +import org.hibernate.spatial.dialect.WithCustomJPAFilter; +import org.hibernate.spatial.jts.EnvelopeAdapter; + +import org.locationtech.jts.geom.Envelope; +import org.locationtech.jts.geom.Geometry; + +/** + * JPA Criteria API {@link Predicate} equivalent of {@link SpatialFilter}. + */ +public class FilterPredicate extends AbstractSimplePredicate implements Serializable { + + private final Expression geometry; + private final Expression filter; + + public FilterPredicate( + CriteriaBuilder criteriaBuilder, Expression geometry, + Expression filter) { + super( (CriteriaBuilderImpl) criteriaBuilder ); + this.geometry = geometry; + this.filter = filter; + } + + public FilterPredicate( + CriteriaBuilder criteriaBuilder, Expression geometry, + Geometry filter) { + this( criteriaBuilder, geometry, criteriaBuilder.literal( filter ) + ); + } + + public FilterPredicate( + CriteriaBuilder criteriaBuilder, Expression geometry, + Envelope envelope, int srid) { + this( criteriaBuilder, geometry, EnvelopeAdapter.toPolygon( envelope, srid ) + ); + } + + @Override + public void registerParameters(ParameterRegistry registry) { + Helper.possibleParameter( geometry, registry ); + Helper.possibleParameter( filter, registry ); + } + + @Override + public String render(boolean isNegated, RenderingContext renderingContext) { + String geometryParameter = ( (Renderable) geometry ).render( renderingContext ); + String filterParameter = ( (Renderable) filter ).render( renderingContext ); + Dialect dialect = renderingContext.getDialect(); + if ( !( dialect instanceof SpatialDialect ) ) { + throw new IllegalStateException( "Dialect must be spatially enabled dialect" ); + } + if ( dialect instanceof WithCustomJPAFilter ) { + return ( (WithCustomJPAFilter) dialect ).filterExpression( geometryParameter, filterParameter ); + } + else { + return SpatialFunction.filter.name() + "(" + geometryParameter + ", " + filterParameter + ") = true"; + } + } +} diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/predicate/SpatialPredicates.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/predicate/SpatialPredicates.java new file mode 100644 index 0000000000..df803ae6e7 --- /dev/null +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/predicate/SpatialPredicates.java @@ -0,0 +1,575 @@ +/* + * 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.spatial.predicate; + +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.Expression; +import javax.persistence.criteria.Predicate; + +import org.hibernate.spatial.SpatialFunction; +import org.hibernate.spatial.criterion.SpatialRestrictions; + +import org.locationtech.jts.geom.Envelope; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.Polygon; + +/** + * A factory for spatial JPA Criteria API {@link Predicate}s. + * + * @author Daniel Shuy + * @see SpatialRestrictions + */ +public final class SpatialPredicates { + + private SpatialPredicates() { + } + + /** + * Create a predicate for testing the arguments for "spatially equal" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry1 geometry expression + * @param geometry2 geometry expression + * + * @return "spatially equal" predicate + * + * @see SpatialRestrictions#eq(String, Geometry) + */ + public static Predicate eq( + CriteriaBuilder criteriaBuilder, Expression geometry1, + Expression geometry2) { + return booleanExpressionToPredicate( + criteriaBuilder, + criteriaBuilder.function( SpatialFunction.equals.toString(), boolean.class, + geometry1, geometry2 + ) + ); + } + + /** + * Create a predicate for testing the arguments for "spatially equal" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry1 geometry expression + * @param geometry2 geometry value + * + * @return "spatially equal" predicate + * + * @see #eq(CriteriaBuilder, Expression, Expression) + */ + public static Predicate eq( + CriteriaBuilder criteriaBuilder, Expression geometry1, + Geometry geometry2) { + return eq( criteriaBuilder, geometry1, + criteriaBuilder.literal( geometry2 ) + ); + } + + /** + * Create a predicate for testing the arguments for "spatially within" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry1 geometry expression + * @param geometry2 geometry expression + * + * @return "spatially within" predicate + * + * @see SpatialRestrictions#within(String, Geometry) + */ + public static Predicate within( + CriteriaBuilder criteriaBuilder, Expression geometry1, + Expression geometry2) { + return booleanExpressionToPredicate( + criteriaBuilder, + criteriaBuilder.function( SpatialFunction.within.toString(), boolean.class, + geometry1, geometry2 + ) + ); + } + + /** + * Create a predicate for testing the arguments for "spatially within" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry1 geometry expression + * @param geometry2 geometry value + * + * @return "spatially within" predicate + * + * @see #within(CriteriaBuilder, Expression, Expression) + */ + public static Predicate within( + CriteriaBuilder criteriaBuilder, Expression geometry1, + Geometry geometry2) { + return within( criteriaBuilder, geometry1, + criteriaBuilder.literal( geometry2 ) + ); + } + + /** + * Create a predicate for testing the arguments for "spatially contains" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry1 geometry expression + * @param geometry2 geometry expression + * + * @return "spatially contains" predicate + * + * @see SpatialRestrictions#contains(String, Geometry) + */ + public static Predicate contains( + CriteriaBuilder criteriaBuilder, Expression geometry1, + Expression geometry2) { + return booleanExpressionToPredicate( + criteriaBuilder, + criteriaBuilder.function( SpatialFunction.contains.toString(), boolean.class, + geometry1, geometry2 + ) + ); + } + + /** + * Create a predicate for testing the arguments for "spatially contains" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry1 geometry expression + * @param geometry2 geometry value + * + * @return "spatially contains" predicate + * + * @see #contains(CriteriaBuilder, Expression, Expression) + */ + public static Predicate contains( + CriteriaBuilder criteriaBuilder, Expression geometry1, + Geometry geometry2) { + return contains( criteriaBuilder, geometry1, + criteriaBuilder.literal( geometry2 ) + ); + } + + /** + * Create a predicate for testing the arguments for "spatially crosses" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry1 geometry expression + * @param geometry2 geometry expression + * + * @return "spatially crosses" predicate + * + * @see SpatialRestrictions#crosses(String, Geometry) + */ + public static Predicate crosses( + CriteriaBuilder criteriaBuilder, Expression geometry1, + Expression geometry2) { + return booleanExpressionToPredicate( + criteriaBuilder, + criteriaBuilder.function( SpatialFunction.crosses.toString(), boolean.class, + geometry1, geometry2 + ) + ); + } + + /** + * Create a predicate for testing the arguments for "spatially crosses" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry1 geometry expression + * @param geometry2 geometry value + * + * @return "spatially crosses" predicate + * + * @see #crosses(CriteriaBuilder, Expression, Expression) + */ + public static Predicate crosses( + CriteriaBuilder criteriaBuilder, Expression geometry1, + Geometry geometry2) { + return crosses( criteriaBuilder, geometry1, + criteriaBuilder.literal( geometry2 ) + ); + } + + /** + * Create a predicate for testing the arguments for "spatially disjoint" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry1 geometry expression + * @param geometry2 geometry expression + * + * @return "spatially disjoint" predicate + * + * @see SpatialRestrictions#disjoint(String, Geometry) + */ + public static Predicate disjoint( + CriteriaBuilder criteriaBuilder, Expression geometry1, + Expression geometry2) { + return booleanExpressionToPredicate( + criteriaBuilder, + criteriaBuilder.function( SpatialFunction.disjoint.toString(), boolean.class, + geometry1, geometry2 + ) + ); + } + + /** + * Create a predicate for testing the arguments for "spatially disjoint" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry1 geometry expression + * @param geometry2 geometry value + * + * @return "spatially disjoint" predicate + * + * @see #disjoint(CriteriaBuilder, Expression, Expression) + */ + public static Predicate disjoint( + CriteriaBuilder criteriaBuilder, Expression geometry1, + Geometry geometry2) { + return disjoint( criteriaBuilder, geometry1, + criteriaBuilder.literal( geometry2 ) + ); + } + + /** + * Create a predicate for testing the arguments for "spatially intersects" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry1 geometry expression + * @param geometry2 geometry expression + * + * @return "spatially intersects" predicate + * + * @see SpatialRestrictions#intersects(String, Geometry) + */ + public static Predicate intersects( + CriteriaBuilder criteriaBuilder, Expression geometry1, + Expression geometry2) { + return booleanExpressionToPredicate( + criteriaBuilder, + criteriaBuilder.function( SpatialFunction.intersects.toString(), boolean.class, + geometry1, geometry2 + ) + ); + } + + /** + * Create a predicate for testing the arguments for "spatially intersects" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry1 geometry expression + * @param geometry2 geometry value + * + * @return "spatially intersects" predicate + * + * @see #intersects(CriteriaBuilder, Expression, Expression) + */ + public static Predicate intersects( + CriteriaBuilder criteriaBuilder, Expression geometry1, + Geometry geometry2) { + return intersects( criteriaBuilder, geometry1, + criteriaBuilder.literal( geometry2 ) + ); + } + + /** + * Create a predicate for testing the arguments for "spatially overlaps" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry1 geometry expression + * @param geometry2 geometry expression + * + * @return "spatially overlaps" predicate + * + * @see SpatialRestrictions#overlaps(String, Geometry) + */ + public static Predicate overlaps( + CriteriaBuilder criteriaBuilder, Expression geometry1, + Expression geometry2) { + return booleanExpressionToPredicate( + criteriaBuilder, + criteriaBuilder.function( SpatialFunction.overlaps.toString(), boolean.class, + geometry1, geometry2 + ) + ); + } + + /** + * Create a predicate for testing the arguments for "spatially overlaps" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry1 geometry expression + * @param geometry2 geometry value + * + * @return "spatially overlaps" predicate + * + * @see #overlaps(CriteriaBuilder, Expression, Expression) + */ + public static Predicate overlaps( + CriteriaBuilder criteriaBuilder, Expression geometry1, + Geometry geometry2) { + return overlaps( criteriaBuilder, geometry1, + criteriaBuilder.literal( geometry2 ) + ); + } + + /** + * Create a predicate for testing the arguments for "spatially touches" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry1 geometry expression + * @param geometry2 geometry expression + * + * @return "spatially touches" predicate + * + * @see SpatialRestrictions#touches(String, Geometry) + */ + public static Predicate touches( + CriteriaBuilder criteriaBuilder, Expression geometry1, + Expression geometry2) { + return booleanExpressionToPredicate( + criteriaBuilder, + criteriaBuilder.function( SpatialFunction.touches.toString(), boolean.class, + geometry1, geometry2 + ) + ); + } + + /** + * Create a predicate for testing the arguments for "spatially touches" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry1 geometry expression + * @param geometry2 geometry value + * + * @return "spatially touches" predicate + * + * @see #touches(CriteriaBuilder, Expression, Expression) + */ + public static Predicate touches( + CriteriaBuilder criteriaBuilder, Expression geometry1, + Geometry geometry2) { + return touches( criteriaBuilder, geometry1, + criteriaBuilder.literal( geometry2 ) + ); + } + + /** + * Create a predicate for testing the arguments for bounding box overlap constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry1 geometry expression + * @param geometry2 geometry expression whose bounding box to use in the comparison + * + * @return bounding box overlap predicate + * + * @see FilterPredicate + * @see SpatialRestrictions#filter(String, Geometry) + */ + public static Predicate filter( + CriteriaBuilder criteriaBuilder, Expression geometry1, + Expression geometry2) { + return new FilterPredicate( criteriaBuilder, geometry1, geometry2 ); + } + + /** + * Create a predicate for testing the arguments for bounding box overlap constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry1 geometry expression + * @param geometry2 geometry value whose bounding box to use in the comparison + * + * @return bounding box overlap predicate + * + * @see FilterPredicate + * @see SpatialRestrictions#filter(String, Geometry) + */ + public static Predicate filter( + CriteriaBuilder criteriaBuilder, Expression geometry1, + Geometry geometry2) { + return new FilterPredicate( criteriaBuilder, geometry1, geometry2 ); + } + + /** + * Create a predicate for testing the arguments for bounding box overlap constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry geometry expression + * @param envelope envelope or bounding box to use in the comparison + * @param srid the SRID of the bounding box + * + * @return bounding box overlap predicate + * + * @see FilterPredicate + * @see SpatialRestrictions#filter(String, Envelope, int) + */ + public static Predicate filterByPolygon( + CriteriaBuilder criteriaBuilder, Expression geometry, + Envelope envelope, int srid) { + return new FilterPredicate( criteriaBuilder, geometry, envelope, srid ); + } + + /** + * Create a predicate for testing the arguments for "distance within" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry1 geometry expression + * @param geometry2 geometry expression + * @param distance distance expression + * + * @return "distance within" predicate + * + * @see SpatialRestrictions#distanceWithin(String, Geometry, double) + */ + public static Predicate distanceWithin( + CriteriaBuilder criteriaBuilder, Expression geometry1, + Expression geometry2, Expression distance) { + return booleanExpressionToPredicate( + criteriaBuilder, + criteriaBuilder.function( SpatialFunction.dwithin.toString(), boolean.class, + geometry1, geometry2, distance + ) + ); + } + + /** + * Create a predicate for testing the arguments for "distance within" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry1 geometry expression + * @param geometry2 geometry value + * @param distance distance expression + * + * @return "distance within" predicate + * + * @see #distanceWithin(CriteriaBuilder, Expression, Expression, Expression) + */ + public static Predicate distanceWithin( + CriteriaBuilder criteriaBuilder, Expression geometry1, + Geometry geometry2, Expression distance) { + return distanceWithin( criteriaBuilder, geometry1, + criteriaBuilder.literal( geometry2 ), distance + ); + } + + /** + * Create a predicate for testing the arguments for "distance within" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry1 geometry expression + * @param geometry2 geometry value + * @param distance distance value + * + * @return "distance within" predicate + * + * @see #distanceWithin(CriteriaBuilder, Expression, Expression, Expression) + */ + public static Predicate distanceWithin( + CriteriaBuilder criteriaBuilder, Expression geometry1, + Geometry geometry2, double distance) { + return distanceWithin( criteriaBuilder, geometry1, + criteriaBuilder.literal( geometry2 ), criteriaBuilder.literal( distance ) + ); + } + + /** + * Create a predicate for testing the arguments for "distance within" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry1 geometry expression + * @param geometry2 geometry expression + * @param distance distance value + * + * @return "distance within" predicate + * + * @see #distanceWithin(CriteriaBuilder, Expression, Expression, Expression) + */ + public static Predicate distanceWithin( + CriteriaBuilder criteriaBuilder, Expression geometry1, + Expression geometry2, double distance) { + return distanceWithin( criteriaBuilder, geometry1, geometry2, + criteriaBuilder.literal( distance ) + ); + } + + /** + * Create a predicate for testing the arguments for "having srid" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry geometry expression + * @param srid SRID expression + * + * @return "having srid" predicate + * + * @see SpatialRestrictions#havingSRID(String, int) + */ + public static Predicate havingSRID( + CriteriaBuilder criteriaBuilder, Expression geometry, + Expression srid) { + return criteriaBuilder.equal( + criteriaBuilder.function( SpatialFunction.srid.toString(), int.class, geometry ), + srid + ); + } + + /** + * Create a predicate for testing the arguments for "having srid" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry geometry expression + * @param srid SRID expression + * + * @return "having srid" predicate + * + * @see #havingSRID(CriteriaBuilder, Expression, Expression) + */ + public static Predicate havingSRID( + CriteriaBuilder criteriaBuilder, Expression geometry, + int srid) { + return havingSRID( criteriaBuilder, geometry, + criteriaBuilder.literal( srid ) + ); + } + + /** + * Create a predicate for testing the arguments for "is empty" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry geometry expression + * + * @return "is empty" predicate + * + * @see SpatialRestrictions#isEmpty(String) + */ + public static Predicate isEmpty(CriteriaBuilder criteriaBuilder, Expression geometry) { + return booleanExpressionToPredicate( + criteriaBuilder, + criteriaBuilder.function( SpatialFunction.isempty.toString(), boolean.class, + geometry + ) + ); + } + + /** + * Create a predicate for testing the arguments for "is not empty" constraint. + * + * @param criteriaBuilder CriteriaBuilder + * @param geometry geometry expression + * + * @return "is not empty" predicate + * + * @see SpatialRestrictions#isNotEmpty(String) + */ + public static Predicate isNotEmpty(CriteriaBuilder criteriaBuilder, Expression geometry) { + return isEmpty( criteriaBuilder, geometry ) + .not(); + } + + private static Predicate booleanExpressionToPredicate( + CriteriaBuilder criteriaBuilder, + Expression expression) { + return criteriaBuilder.equal( expression, true ); + } +} diff --git a/hibernate-spatial/src/test/java/org/hibernate/spatial/integration/TestSpatialPredicates.java b/hibernate-spatial/src/test/java/org/hibernate/spatial/integration/TestSpatialPredicates.java new file mode 100644 index 0000000000..45351f69e0 --- /dev/null +++ b/hibernate-spatial/src/test/java/org/hibernate/spatial/integration/TestSpatialPredicates.java @@ -0,0 +1,264 @@ +/* + * 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.spatial.integration; + +import java.sql.SQLException; +import java.util.List; +import java.util.Map; +import java.util.function.BiFunction; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; + +import org.hibernate.Session; +import org.hibernate.Transaction; +import org.hibernate.spatial.HSMessageLogger; +import org.hibernate.spatial.SpatialFunction; +import org.hibernate.spatial.dialect.hana.HANASpatialDialect; +import org.hibernate.spatial.integration.jts.JtsGeomEntity; +import org.hibernate.spatial.predicate.SpatialPredicates; +import org.hibernate.spatial.testing.SpatialDialectMatcher; +import org.hibernate.spatial.testing.SpatialFunctionalTestCase; + +import org.hibernate.testing.Skip; +import org.hibernate.testing.SkipForDialect; +import org.junit.Test; + +import org.jboss.logging.Logger; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +/** + * @see TestSpatialRestrictions + */ +@Skip(condition = SpatialDialectMatcher.class, message = "No Spatial Dialect") +@SkipForDialect(value = HANASpatialDialect.class, comment = "The HANA dialect is tested via org.hibernate.spatial.dialect.hana.TestHANASpatialFunctions", jiraKey = "HHH-12426") +public class TestSpatialPredicates extends SpatialFunctionalTestCase { + + private static HSMessageLogger LOG = Logger.getMessageLogger( + HSMessageLogger.class, + TestSpatialPredicates.class.getName() + ); + + protected HSMessageLogger getLogger() { + return LOG; + } + + @Test + public void within() throws SQLException { + if ( !isSupportedByDialect( SpatialFunction.within ) ) { + return; + } + Map dbexpected = expectationsFactory.getWithin( expectationsFactory.getTestPolygon() ); + BiFunction, Predicate> predicateFactory = (criteriaBuilder, root) -> + SpatialPredicates.within( criteriaBuilder, root.get( "geom" ), expectationsFactory.getTestPolygon() ); + retrieveAndCompare( dbexpected, predicateFactory ); + } + + @Test + public void filter() throws SQLException { + if ( !dialectSupportsFiltering() ) { + LOG.info( "Filtering is not supported by Dialect" ); + return; + } + Map dbexpected = expectationsFactory.getFilter( expectationsFactory.getTestPolygon() ); + BiFunction, Predicate> predicateFactory = (criteriaBuilder, root) -> + SpatialPredicates.filter( criteriaBuilder, root.get( "geom" ), expectationsFactory.getTestPolygon() ); + retrieveAndCompare( dbexpected, predicateFactory ); + } + + @Test + public void contains() throws SQLException { + if ( !isSupportedByDialect( SpatialFunction.contains ) ) { + return; + } + Map dbexpected = expectationsFactory.getContains( expectationsFactory.getTestPolygon() ); + BiFunction, Predicate> predicateFactory = (criteriaBuilder, root) -> + SpatialPredicates.contains( + criteriaBuilder, + root.get( "geom" ), + expectationsFactory.getTestPolygon() + ); + retrieveAndCompare( dbexpected, predicateFactory ); + } + + @Test + public void crosses() throws SQLException { + if ( !isSupportedByDialect( SpatialFunction.crosses ) ) { + return; + } + Map dbexpected = expectationsFactory.getCrosses( expectationsFactory.getTestPolygon() ); + BiFunction, Predicate> predicateFactory = (criteriaBuilder, root) -> + SpatialPredicates.crosses( criteriaBuilder, root.get( "geom" ), expectationsFactory.getTestPolygon() ); + retrieveAndCompare( dbexpected, predicateFactory ); + } + + @Test + public void touches() throws SQLException { + if ( !isSupportedByDialect( SpatialFunction.touches ) ) { + return; + } + Map dbexpected = expectationsFactory.getTouches( expectationsFactory.getTestPolygon() ); + BiFunction, Predicate> predicateFactory = (criteriaBuilder, root) -> + SpatialPredicates.touches( criteriaBuilder, root.get( "geom" ), expectationsFactory.getTestPolygon() ); + retrieveAndCompare( dbexpected, predicateFactory ); + } + + @Test + public void disjoint() throws SQLException { + if ( !isSupportedByDialect( SpatialFunction.disjoint ) ) { + return; + } + Map dbexpected = expectationsFactory.getDisjoint( expectationsFactory.getTestPolygon() ); + BiFunction, Predicate> predicateFactory = (criteriaBuilder, root) -> + SpatialPredicates.disjoint( + criteriaBuilder, + root.get( "geom" ), + expectationsFactory.getTestPolygon() + ); + retrieveAndCompare( dbexpected, predicateFactory ); + } + + @Test + public void eq() throws SQLException { + if ( !isSupportedByDialect( SpatialFunction.equals ) ) { + return; + } + Map dbexpected = expectationsFactory.getEquals( expectationsFactory.getTestPolygon() ); + BiFunction, Predicate> predicateFactory = (criteriaBuilder, root) -> + SpatialPredicates.eq( criteriaBuilder, root.get( "geom" ), expectationsFactory.getTestPolygon() ); + retrieveAndCompare( dbexpected, predicateFactory ); + } + + @Test + public void intersects() throws SQLException { + if ( !isSupportedByDialect( SpatialFunction.intersects ) ) { + return; + } + Map dbexpected = expectationsFactory.getIntersects( expectationsFactory.getTestPolygon() ); + BiFunction, Predicate> predicateFactory = (criteriaBuilder, root) -> + SpatialPredicates.intersects( + criteriaBuilder, + root.get( "geom" ), + expectationsFactory.getTestPolygon() + ); + retrieveAndCompare( dbexpected, predicateFactory ); + } + + @Test + public void overlaps() throws SQLException { + if ( !isSupportedByDialect( SpatialFunction.overlaps ) ) { + return; + } + Map dbexpected = expectationsFactory.getOverlaps( expectationsFactory.getTestPolygon() ); + BiFunction, Predicate> predicateFactory = (criteriaBuilder, root) -> + SpatialPredicates.overlaps( + criteriaBuilder, + root.get( "geom" ), + expectationsFactory.getTestPolygon() + ); + retrieveAndCompare( dbexpected, predicateFactory ); + } + + @Test + public void dwithin() throws SQLException { + if ( !isSupportedByDialect( SpatialFunction.dwithin ) ) { + return; + } + Map dbexpected = expectationsFactory.getDwithin( expectationsFactory.getTestPoint(), 30.0 ); + BiFunction, Predicate> predicateFactory = (criteriaBuilder, root) -> + SpatialPredicates.distanceWithin( + criteriaBuilder, + root.get( "geom" ), + expectationsFactory.getTestPoint(), + 30.0 + ); + retrieveAndCompare( dbexpected, predicateFactory ); + } + + @Test + public void isEmpty() throws SQLException { + if ( !isSupportedByDialect( SpatialFunction.isempty ) ) { + return; + } + Map dbexpected = expectationsFactory.getIsEmpty(); + BiFunction, Predicate> predicateFactory = (criteriaBuilder, root) -> + SpatialPredicates.isEmpty( criteriaBuilder, root.get( "geom" ) ); + retrieveAndCompare( dbexpected, predicateFactory ); + } + + @Test + public void isNotEmpty() throws SQLException { + if ( !isSupportedByDialect( SpatialFunction.isempty ) ) { + return; + } + Map dbexpected = expectationsFactory.getIsNotEmpty(); + BiFunction, Predicate> predicateFactory = (criteriaBuilder, root) -> + SpatialPredicates.isNotEmpty( criteriaBuilder, root.get( "geom" ) ); + retrieveAndCompare( dbexpected, predicateFactory ); + } + + @Test + public void havingSRID() throws SQLException { + if ( !isSupportedByDialect( SpatialFunction.srid ) ) { + return; + } + Map dbexpected = expectationsFactory.havingSRID( 4326 ); + BiFunction, Predicate> predicateFactory = (criteriaBuilder, root) -> + SpatialPredicates.havingSRID( criteriaBuilder, root.get( "geom" ), 4326 ); + retrieveAndCompare( dbexpected, predicateFactory ); + dbexpected = expectationsFactory.havingSRID( 31370 ); + predicateFactory = (criteriaBuilder, root) -> + SpatialPredicates.havingSRID( criteriaBuilder, root.get( "geom" ), 31370 ); + retrieveAndCompare( dbexpected, predicateFactory ); + } + + private void retrieveAndCompare( + Map dbexpected, + BiFunction, Predicate> predicateFactory) { + try (Session session = openSession()) { + Transaction tx = null; + try { + tx = session.beginTransaction(); + CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder(); + CriteriaQuery criteriaQuery = criteriaBuilder.createQuery( JtsGeomEntity.class ); + Root root = criteriaQuery.from( JtsGeomEntity.class ); + criteriaQuery.select( root ) + .where( predicateFactory.apply( criteriaBuilder, root ) ); + List list = session.createQuery( criteriaQuery ) + .getResultList(); + compare( dbexpected, list ); + } + finally { + if ( tx != null ) { + tx.rollback(); + } + } + } + } + + private void compare(Map dbexpected, List list) { + int cnt = dbexpected.entrySet() + .stream() + .filter( Map.Entry::getValue ) + .reduce( 0, (accumulator, entry) -> { + if ( !findInList( entry.getKey(), list ) ) { + fail( String.format( "Expected object with id= %d, but not found in result", entry.getKey() ) ); + } + return accumulator + 1; + }, Integer::sum ); + assertEquals( cnt, list.size() ); + LOG.infof( "Found %d objects within testsuite-suite polygon.", cnt ); + } + + private boolean findInList(Integer id, List list) { + return list.stream() + .anyMatch( entity -> entity.getId().equals( id ) ); + } +} diff --git a/hibernate-spatial/src/test/java/org/hibernate/spatial/testing/TestSupportFactories.java b/hibernate-spatial/src/test/java/org/hibernate/spatial/testing/TestSupportFactories.java index 6dd4dda86e..d8fab275b2 100644 --- a/hibernate-spatial/src/test/java/org/hibernate/spatial/testing/TestSupportFactories.java +++ b/hibernate-spatial/src/test/java/org/hibernate/spatial/testing/TestSupportFactories.java @@ -38,7 +38,6 @@ public class TestSupportFactories { private static Class getSupportFactoryClass(Dialect dialect) { String canonicalName = dialect.getClass().getCanonicalName(); - if ( ( dialect instanceof SpatialDialect ) && PostgreSQL82Dialect.class.isAssignableFrom( dialect.getClass() ) ) { //this test works because all postgis dialects ultimately derive of the Postgresql82Dialect return PostgisTestSupport.class; diff --git a/hibernate-spatial/src/test/java/org/hibernate/spatial/testing/dialects/postgis/PostgisTestSupport.java b/hibernate-spatial/src/test/java/org/hibernate/spatial/testing/dialects/postgis/PostgisTestSupport.java index 22ccf23b94..4a07688c05 100644 --- a/hibernate-spatial/src/test/java/org/hibernate/spatial/testing/dialects/postgis/PostgisTestSupport.java +++ b/hibernate-spatial/src/test/java/org/hibernate/spatial/testing/dialects/postgis/PostgisTestSupport.java @@ -8,6 +8,9 @@ package org.hibernate.spatial.testing.dialects.postgis; +import org.hibernate.spatial.integration.TestSpatialFunctions; +import org.hibernate.spatial.integration.TestSpatialPredicates; +import org.hibernate.spatial.integration.TestSpatialRestrictions; import org.hibernate.spatial.testing.AbstractExpectationsFactory; import org.hibernate.spatial.testing.DataSourceUtils; import org.hibernate.spatial.testing.SQLExpressionTemplate; @@ -24,8 +27,10 @@ public class PostgisTestSupport extends TestSupport { public TestData createTestData(BaseCoreFunctionalTestCase testcase) { - if ( testcase.getClass().getCanonicalName().contains( "TestSpatialFunctions" ) || - testcase.getClass().getCanonicalName().contains( "TestSpatialRestrictions" ) ) { + Class testcaseClass = testcase.getClass(); + if ( testcaseClass == TestSpatialFunctions.class || + testcaseClass == TestSpatialRestrictions.class || + testcaseClass == TestSpatialPredicates.class ) { return TestData.fromFile( "postgis-functions-test.xml" ); } return TestData.fromFile( "test-data-set.xml" ); From b060b21851d0330a7d75145b6976d0a9b39c6093 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Fri, 21 Feb 2020 15:24:04 -0800 Subject: [PATCH 14/27] HHH-13875 : Added test cases --- .../OptionalOneToOneMapsIdQueryTest.java | 297 ++++++++++++++++++ .../OptionalOneToOnePKJCQueryTest.java | 289 +++++++++++++++++ 2 files changed, 586 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OptionalOneToOneMapsIdQueryTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OptionalOneToOnePKJCQueryTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OptionalOneToOneMapsIdQueryTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OptionalOneToOneMapsIdQueryTest.java new file mode 100644 index 0000000000..a74fab7729 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OptionalOneToOneMapsIdQueryTest.java @@ -0,0 +1,297 @@ +/* + * 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.onetoone; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.MapsId; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +@TestForIssue( jiraKey = "HHH-13875") +public class OptionalOneToOneMapsIdQueryTest extends BaseNonConfigCoreFunctionalTestCase { + + @Test + public void testOneToOneWithIdNamedId() { + // Test with associated entity having ID named "id" + doInHibernate( this::sessionFactory, session -> { + BarWithIdNamedId bar = new BarWithIdNamedId(); + bar.id = 1L; + bar.longValue = 2L; + FooHasBarWithIdNamedId foo = new FooHasBarWithIdNamedId(); + foo.id = 1L; + foo.bar = bar; + session.persist( bar ); + session.persist( foo ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithIdNamedId foo = session.createQuery( + "from FooHasBarWithIdNamedId where bar.id = ?1", + FooHasBarWithIdNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNotNull( foo ); + assertNotNull( foo.bar ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithIdNamedId foo = session.get( FooHasBarWithIdNamedId.class, 1L ); + session.delete( foo.bar ); + foo.bar = null; + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithIdNamedId foo = session.createQuery( + "from FooHasBarWithIdNamedId where bar.id = ?1", + FooHasBarWithIdNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNull( foo ); + }); + } + + @Test + public void testOneToOneWithNoIdOrPropNamedId() { + // Test with associated entity having ID not named "id", and with no property named "id" + doInHibernate( this::sessionFactory, session -> { + BarWithNoIdOrPropNamedId bar = new BarWithNoIdOrPropNamedId(); + bar.barId = 1L; + bar.longValue = 2L; + FooHasBarWithNoIdOrPropNamedId foo = new FooHasBarWithNoIdOrPropNamedId(); + foo.id = 1L; + foo.bar = bar; + session.persist( bar ); + session.persist( foo ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNoIdOrPropNamedId foo = session.createQuery( + "from FooHasBarWithNoIdOrPropNamedId where bar.barId = ?1", + FooHasBarWithNoIdOrPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNotNull( foo ); + assertNotNull( foo.bar ); + }); + + // Querying by the generic "id" should work the same as "barId". + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNoIdOrPropNamedId foo = session.createQuery( + "from FooHasBarWithNoIdOrPropNamedId where bar.id = ?1", + FooHasBarWithNoIdOrPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNotNull( foo ); + assertNotNull( foo.bar ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNoIdOrPropNamedId foo = session.get( FooHasBarWithNoIdOrPropNamedId.class, 1L ); + session.delete( foo.bar ); + foo.bar = null; + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNoIdOrPropNamedId foo = session.createQuery( + "from FooHasBarWithNoIdOrPropNamedId where bar.barId = ?1", + FooHasBarWithNoIdOrPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNull( foo ); + }); + + // Querying by the generic "id" should work the same as "barId". + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNoIdOrPropNamedId foo = session.createQuery( + "from FooHasBarWithNoIdOrPropNamedId where bar.id = ?1", + FooHasBarWithNoIdOrPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNull( foo ); + }); + } + + @Test + public void testOneToOneWithNonIdPropNamedId() { + // Test with associated entity having a non-ID property named "id" + doInHibernate( this::sessionFactory, session -> { + BarWithNonIdPropNamedId bar = new BarWithNonIdPropNamedId(); + bar.barId = 1L; + bar.id = 2L; + FooHasBarWithNonIdPropNamedId foo = new FooHasBarWithNonIdPropNamedId(); + foo.id = 1L; + foo.bar = bar; + session.persist( bar ); + session.persist( foo ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNonIdPropNamedId foo = session.createQuery( + "from FooHasBarWithNonIdPropNamedId where bar.barId = ?1", + FooHasBarWithNonIdPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNotNull( foo ); + assertNotNull( foo.bar ); + }); + + // bar.id is a non-ID property. + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNonIdPropNamedId foo = session.createQuery( + "from FooHasBarWithNonIdPropNamedId where bar.id = ?1", + FooHasBarWithNonIdPropNamedId.class + ).setParameter( 1, 2L ) + .uniqueResult(); + assertNotNull( foo ); + assertNotNull( foo.bar ); + }); + + // bar.id is a non-ID property. + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNonIdPropNamedId foo = session.createQuery( + "from FooHasBarWithNonIdPropNamedId where bar.id = ?1", + FooHasBarWithNonIdPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNull( foo ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNonIdPropNamedId foo = session.get( FooHasBarWithNonIdPropNamedId.class, 1L ); + session.delete( foo.bar ); + foo.bar = null; + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNonIdPropNamedId foo = session.createQuery( + "from FooHasBarWithNonIdPropNamedId where bar.barId = ?1", + FooHasBarWithNonIdPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNull( foo ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNonIdPropNamedId foo = session.createQuery( + "from FooHasBarWithNonIdPropNamedId where bar.id = ?1", + FooHasBarWithNonIdPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNull( foo ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNonIdPropNamedId foo = session.createQuery( + "from FooHasBarWithNonIdPropNamedId where bar.id = ?1", + FooHasBarWithNonIdPropNamedId.class + ).setParameter( 1, 2L ) + .uniqueResult(); + assertNull( foo ); + }); + } + + @After + public void cleanupData() { + doInHibernate( this::sessionFactory, session -> { + session.createQuery( "delete from FooHasBarWithIdNamedId" ).executeUpdate(); + session.createQuery( "delete from FooHasBarWithNoIdOrPropNamedId" ).executeUpdate(); + session.createQuery( "delete from FooHasBarWithNonIdPropNamedId" ).executeUpdate(); + session.createQuery( "delete from BarWithIdNamedId" ).executeUpdate(); + session.createQuery( "delete from BarWithNoIdOrPropNamedId" ).executeUpdate(); + session.createQuery( "delete from BarWithNoIdOrPropNamedId" ).executeUpdate(); + }); + } + + @Override + protected Class[] getAnnotatedClasses() + { + return new Class[] { + FooHasBarWithIdNamedId.class, + BarWithIdNamedId.class, + FooHasBarWithNoIdOrPropNamedId.class, + BarWithNoIdOrPropNamedId.class, + FooHasBarWithNonIdPropNamedId.class, + BarWithNonIdPropNamedId.class + }; + } + + @Entity(name = "FooHasBarWithIdNamedId") + public static class FooHasBarWithIdNamedId + { + @Id + private Long id; + + @OneToOne(optional = true) + @MapsId + @JoinColumn(name = "id") + @NotFound(action = NotFoundAction.IGNORE) + private BarWithIdNamedId bar; + } + + @Entity(name = "BarWithIdNamedId") + public static class BarWithIdNamedId { + @Id + private long id; + private long longValue; + } + + @Entity(name = "FooHasBarWithNoIdOrPropNamedId") + @Table(name = "FooHasBarNoIdOrPropNamedId") + public static class FooHasBarWithNoIdOrPropNamedId + { + @Id + private Long id; + + @OneToOne(optional = true) + @MapsId + @JoinColumn(name = "id") + @NotFound(action = NotFoundAction.IGNORE) + private BarWithNoIdOrPropNamedId bar; + } + + @Entity(name = "BarWithNoIdOrPropNamedId") + public static class BarWithNoIdOrPropNamedId { + @Id + private long barId; + private long longValue; + } + + @Entity(name = "FooHasBarWithNonIdPropNamedId") + @Table(name = "FooHasBarNonIdPropNamedId") + public static class FooHasBarWithNonIdPropNamedId + { + @Id + private Long id; + + @OneToOne(optional = true) + @MapsId + @JoinColumn(name = "id") + @NotFound(action = NotFoundAction.IGNORE) + private BarWithNonIdPropNamedId bar; + } + + @Entity(name = "BarWithNonIdPropNamedId") + public static class BarWithNonIdPropNamedId { + @Id + private long barId; + private long id; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OptionalOneToOnePKJCQueryTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OptionalOneToOnePKJCQueryTest.java new file mode 100644 index 0000000000..caf7fef69c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/onetoone/OptionalOneToOnePKJCQueryTest.java @@ -0,0 +1,289 @@ +/* + * 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.onetoone; + +import javax.persistence.ConstraintMode; +import javax.persistence.Entity; +import javax.persistence.ForeignKey; +import javax.persistence.Id; +import javax.persistence.OneToOne; +import javax.persistence.PrimaryKeyJoinColumn; +import javax.persistence.Table; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +@TestForIssue( jiraKey = "HHH-13875") +public class OptionalOneToOnePKJCQueryTest extends BaseNonConfigCoreFunctionalTestCase { + + @Test + public void testOneToOneWithIdNamedId() { + // Test with associated entity having ID named "id" + doInHibernate( this::sessionFactory, session -> { + BarWithIdNamedId bar = new BarWithIdNamedId(); + bar.id = 1L; + bar.longValue = 2L; + FooHasBarWithIdNamedId foo = new FooHasBarWithIdNamedId(); + foo.id = 1L; + foo.bar = bar; + session.persist( bar ); + session.persist( foo ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithIdNamedId foo = session.createQuery( + "from FooHasBarWithIdNamedId where bar.id = ?1", + FooHasBarWithIdNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNotNull( foo ); + assertNotNull( foo.bar ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithIdNamedId foo = session.get( FooHasBarWithIdNamedId.class, 1L ); + session.delete( foo.bar ); + foo.bar = null; + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithIdNamedId foo = session.createQuery( + "from FooHasBarWithIdNamedId where bar.id = ?1", + FooHasBarWithIdNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNull( foo ); + }); + } + + @Test + public void testOneToOneWithNoIdOrPropNamedId() { + // Test with associated entity having ID not named "id", and with no property named "id" + doInHibernate( this::sessionFactory, session -> { + BarWithNoIdOrPropNamedId bar = new BarWithNoIdOrPropNamedId(); + bar.barId = 1L; + bar.longValue = 2L; + FooHasBarWithNoIdOrPropNamedId foo = new FooHasBarWithNoIdOrPropNamedId(); + foo.id = 1L; + foo.bar = bar; + session.persist( bar ); + session.persist( foo ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNoIdOrPropNamedId foo = session.createQuery( + "from FooHasBarWithNoIdOrPropNamedId where bar.barId = ?1", + FooHasBarWithNoIdOrPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNotNull( foo ); + assertNotNull( foo.bar ); + }); + + // Querying by the generic "id" should work the same as "barId". + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNoIdOrPropNamedId foo = session.createQuery( + "from FooHasBarWithNoIdOrPropNamedId where bar.id = ?1", + FooHasBarWithNoIdOrPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNotNull( foo ); + assertNotNull( foo.bar ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNoIdOrPropNamedId foo = session.get( FooHasBarWithNoIdOrPropNamedId.class, 1L ); + session.delete( foo.bar ); + foo.bar = null; + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNoIdOrPropNamedId foo = session.createQuery( + "from FooHasBarWithNoIdOrPropNamedId where bar.barId = ?1", + FooHasBarWithNoIdOrPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNull( foo ); + }); + + // Querying by the generic "id" should work the same as "barId". + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNoIdOrPropNamedId foo = session.createQuery( + "from FooHasBarWithNoIdOrPropNamedId where bar.id = ?1", + FooHasBarWithNoIdOrPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNull( foo ); + }); + } + + @Test + public void testOneToOneWithNonIdPropNamedId() { + // Test with associated entity having a non-ID property named "id" + doInHibernate( this::sessionFactory, session -> { + BarWithNonIdPropNamedId bar = new BarWithNonIdPropNamedId(); + bar.barId = 1L; + bar.id = 2L; + FooHasBarWithNonIdPropNamedId foo = new FooHasBarWithNonIdPropNamedId(); + foo.id = 1L; + foo.bar = bar; + session.persist( bar ); + session.persist( foo ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNonIdPropNamedId foo = session.createQuery( + "from FooHasBarWithNonIdPropNamedId where bar.barId = ?1", + FooHasBarWithNonIdPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNotNull( foo ); + assertNotNull( foo.bar ); + }); + + // bar.id is a non-ID property. + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNonIdPropNamedId foo = session.createQuery( + "from FooHasBarWithNonIdPropNamedId where bar.id = ?1", + FooHasBarWithNonIdPropNamedId.class + ).setParameter( 1, 2L ) + .uniqueResult(); + assertNotNull( foo ); + assertNotNull( foo.bar ); + }); + + // bar.id is a non-ID property. + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNonIdPropNamedId foo = session.createQuery( + "from FooHasBarWithNonIdPropNamedId where bar.id = ?1", + FooHasBarWithNonIdPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNull( foo ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNonIdPropNamedId foo = session.get( FooHasBarWithNonIdPropNamedId.class, 1L ); + session.delete( foo.bar ); + foo.bar = null; + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNonIdPropNamedId foo = session.createQuery( + "from FooHasBarWithNonIdPropNamedId where bar.barId = ?1", + FooHasBarWithNonIdPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNull( foo ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNonIdPropNamedId foo = session.createQuery( + "from FooHasBarWithNonIdPropNamedId where bar.id = ?1", + FooHasBarWithNonIdPropNamedId.class + ).setParameter( 1, 1L ) + .uniqueResult(); + assertNull( foo ); + }); + + doInHibernate( this::sessionFactory, session -> { + final FooHasBarWithNonIdPropNamedId foo = session.createQuery( + "from FooHasBarWithNonIdPropNamedId where bar.id = ?1", + FooHasBarWithNonIdPropNamedId.class + ).setParameter( 1, 2L ) + .uniqueResult(); + assertNull( foo ); + }); + } + + @After + public void cleanupData() { + doInHibernate( this::sessionFactory, session -> { + session.createQuery( "delete from FooHasBarWithIdNamedId" ).executeUpdate(); + session.createQuery( "delete from FooHasBarWithNoIdOrPropNamedId" ).executeUpdate(); + session.createQuery( "delete from FooHasBarWithNonIdPropNamedId" ).executeUpdate(); + session.createQuery( "delete from BarWithIdNamedId" ).executeUpdate(); + session.createQuery( "delete from BarWithNoIdOrPropNamedId" ).executeUpdate(); + session.createQuery( "delete from BarWithNoIdOrPropNamedId" ).executeUpdate(); + }); + } + + @Override + protected Class[] getAnnotatedClasses() + { + return new Class[] { + FooHasBarWithIdNamedId.class, + BarWithIdNamedId.class, + FooHasBarWithNoIdOrPropNamedId.class, + BarWithNoIdOrPropNamedId.class, + FooHasBarWithNonIdPropNamedId.class, + BarWithNonIdPropNamedId.class + }; + } + + @Entity(name = "FooHasBarWithIdNamedId") + public static class FooHasBarWithIdNamedId + { + @Id + private long id; + + @OneToOne(optional = true) + @PrimaryKeyJoinColumn(foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT)) + private BarWithIdNamedId bar; + } + + @Entity(name = "BarWithIdNamedId") + public static class BarWithIdNamedId { + @Id + private long id; + private long longValue; + } + + @Entity(name = "FooHasBarWithNoIdOrPropNamedId") + @Table(name = "FooHasBarNoIdOrPropNamedId") + public static class FooHasBarWithNoIdOrPropNamedId + { + @Id + private long id; + + @OneToOne(optional = true) + @PrimaryKeyJoinColumn() + private BarWithNoIdOrPropNamedId bar; + } + + @Entity(name = "BarWithNoIdOrPropNamedId") + public static class BarWithNoIdOrPropNamedId { + @Id + private long barId; + private long longValue; + } + + @Entity(name = "FooHasBarWithNonIdPropNamedId") + @Table(name = "FooHasBarNonIdPropNamedId") + public static class FooHasBarWithNonIdPropNamedId + { + @Id + private long id; + + @OneToOne(optional = true) + @PrimaryKeyJoinColumn() + private BarWithNonIdPropNamedId bar; + } + + @Entity(name = "BarWithNonIdPropNamedId") + public static class BarWithNonIdPropNamedId { + @Id + private long barId; + private long id; + } +} From a7261ad0533f8cc1eabd299a62e5daef8735faae Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Fri, 21 Feb 2020 15:32:29 -0800 Subject: [PATCH 15/27] HHH-13875 : Optional one-to-one does not always join the associated entity table when querying --- .../org/hibernate/hql/internal/ast/tree/DotNode.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/DotNode.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/DotNode.java index 71cb7d8c99..426b5c6838 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/DotNode.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/tree/DotNode.java @@ -394,11 +394,14 @@ public class DotNode extends FromReferenceNode implements DisplayableNode, Selec if ( isDotNode( parent ) ) { // our parent is another dot node, meaning we are being further dereferenced. - // thus we need to generate a join unless the parent refers to the associated - // entity's PK (because 'our' table would know the FK). + // thus we need to generate a join unless the association is non-nullable and + // parent refers to the associated entity's PK (because 'our' table would know the FK). parentAsDotNode = (DotNode) parent; property = parentAsDotNode.propertyName; - joinIsNeeded = generateJoin && !isPropertyEmbeddedInJoinProperties( parentAsDotNode.propertyName ); + joinIsNeeded = generateJoin && ( + entityType.isNullable() || + !isPropertyEmbeddedInJoinProperties( parentAsDotNode.propertyName ) + ); } else if ( !getWalker().isSelectStatement() ) { // in non-select queries, the only time we should need to join is if we are in a subquery from clause From 5501a40dd20cd21c96a142979276b746cfd0f728 Mon Sep 17 00:00:00 2001 From: The Geeky Asian Date: Sat, 8 Feb 2020 01:31:41 +0400 Subject: [PATCH 16/27] HHH-13855 - Remove unnecessary delared variable JtaManager in HibernatePersistenceProviderAdaptor In the HibernatePersistenceProviderAdaptor, there is an unnecessary declaration of JtaManager. The sole purpose of having this variable is only to use in the overridden method named InjectJtaManager. This was be improved by removing the JtaManager as there is no use of the variable, as of now at least. --- .../hibernate5/HibernatePersistenceProviderAdaptor.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/hibernate-jipijapa/src/main/java/org/jboss/as/jpa/hibernate5/HibernatePersistenceProviderAdaptor.java b/hibernate-jipijapa/src/main/java/org/jboss/as/jpa/hibernate5/HibernatePersistenceProviderAdaptor.java index d8a330d830..66e66dc727 100644 --- a/hibernate-jipijapa/src/main/java/org/jboss/as/jpa/hibernate5/HibernatePersistenceProviderAdaptor.java +++ b/hibernate-jipijapa/src/main/java/org/jboss/as/jpa/hibernate5/HibernatePersistenceProviderAdaptor.java @@ -43,17 +43,11 @@ public class HibernatePersistenceProviderAdaptor implements PersistenceProviderA @SuppressWarnings("WeakerAccess") public static final String HIBERNATE_EXTENDED_BEANMANAGER = "org.hibernate.resource.beans.container.spi.ExtendedBeanManager"; - private volatile JtaManager jtaManager; private volatile Platform platform; private static final String NONE = SharedCacheMode.NONE.name(); @Override - public void injectJtaManager(JtaManager jtaManager) { - // todo : why? `this.jtaManager` is never used aside from setting here in this method - if ( this.jtaManager != jtaManager ) { - this.jtaManager = jtaManager; - } - } + public void injectJtaManager(JtaManager jtaManager) { } @Override public void injectPlatform(Platform platform) { From c7aaf31cbed5d41ea85f406d90d4bd16530370a5 Mon Sep 17 00:00:00 2001 From: Vlad Mihalcea Date: Tue, 25 Feb 2020 16:33:15 +0200 Subject: [PATCH 17/27] HHH-13879 - Slow query log should use System#nanoTime not System#currentTimeMillis --- .../jdbc/internal/ResultSetReturnImpl.java | 42 +++++++++---------- .../engine/jdbc/spi/SqlStatementLogger.java | 23 +++++----- 2 files changed, 33 insertions(+), 32 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/ResultSetReturnImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/ResultSetReturnImpl.java index c39549eced..e3b64b3f7a 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/ResultSetReturnImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/internal/ResultSetReturnImpl.java @@ -46,9 +46,9 @@ public class ResultSetReturnImpl implements ResultSetReturn { @Override public ResultSet extract(PreparedStatement statement) { // IMPL NOTE : SQL logged by caller - long executeStart = 0; + long executeStartNanos = 0; if ( this.sqlStatementLogger.getLogSlowQuery() > 0 ) { - executeStart = System.currentTimeMillis(); + executeStartNanos = System.nanoTime(); } try { final ResultSet rs; @@ -58,7 +58,7 @@ public class ResultSetReturnImpl implements ResultSetReturn { } finally { jdbcExecuteStatementEnd(); - sqlStatementLogger.logSlowQuery( statement, executeStart ); + sqlStatementLogger.logSlowQuery( statement, executeStartNanos ); } postExtract( rs, statement ); return rs; @@ -79,9 +79,9 @@ public class ResultSetReturnImpl implements ResultSetReturn { @Override public ResultSet extract(CallableStatement callableStatement) { // IMPL NOTE : SQL logged by caller - long executeStart = 0; + long executeStartNanos = 0; if ( this.sqlStatementLogger.getLogSlowQuery() > 0 ) { - executeStart = System.currentTimeMillis(); + executeStartNanos = System.nanoTime(); } try { final ResultSet rs; @@ -91,7 +91,7 @@ public class ResultSetReturnImpl implements ResultSetReturn { } finally { jdbcExecuteStatementEnd(); - sqlStatementLogger.logSlowQuery( callableStatement, executeStart ); + sqlStatementLogger.logSlowQuery( callableStatement, executeStartNanos ); } postExtract( rs, callableStatement ); return rs; @@ -104,9 +104,9 @@ public class ResultSetReturnImpl implements ResultSetReturn { @Override public ResultSet extract(Statement statement, String sql) { sqlStatementLogger.logStatement( sql ); - long executeStart = 0; + long executeStartNanos = 0; if ( this.sqlStatementLogger.getLogSlowQuery() > 0 ) { - executeStart = System.currentTimeMillis(); + executeStartNanos = System.nanoTime(); } try { final ResultSet rs; @@ -116,7 +116,7 @@ public class ResultSetReturnImpl implements ResultSetReturn { } finally { jdbcExecuteStatementEnd(); - sqlStatementLogger.logSlowQuery( sql, executeStart ); + sqlStatementLogger.logSlowQuery( sql, executeStartNanos ); } postExtract( rs, statement ); return rs; @@ -129,9 +129,9 @@ public class ResultSetReturnImpl implements ResultSetReturn { @Override public ResultSet execute(PreparedStatement statement) { // sql logged by StatementPreparerImpl - long executeStart = 0; + long executeStartNanos = 0; if ( this.sqlStatementLogger.getLogSlowQuery() > 0 ) { - executeStart = System.currentTimeMillis(); + executeStartNanos = System.nanoTime(); } try { final ResultSet rs; @@ -146,7 +146,7 @@ public class ResultSetReturnImpl implements ResultSetReturn { } finally { jdbcExecuteStatementEnd(); - sqlStatementLogger.logSlowQuery( statement, executeStart ); + sqlStatementLogger.logSlowQuery( statement, executeStartNanos ); } postExtract( rs, statement ); return rs; @@ -159,9 +159,9 @@ public class ResultSetReturnImpl implements ResultSetReturn { @Override public ResultSet execute(Statement statement, String sql) { sqlStatementLogger.logStatement( sql ); - long executeStart = 0; + long executeStartNanos = 0; if ( this.sqlStatementLogger.getLogSlowQuery() > 0 ) { - executeStart = System.currentTimeMillis(); + executeStartNanos = System.nanoTime(); } try { final ResultSet rs; @@ -176,7 +176,7 @@ public class ResultSetReturnImpl implements ResultSetReturn { } finally { jdbcExecuteStatementEnd(); - sqlStatementLogger.logSlowQuery( statement, executeStart ); + sqlStatementLogger.logSlowQuery( statement, executeStartNanos ); } postExtract( rs, statement ); return rs; @@ -188,9 +188,9 @@ public class ResultSetReturnImpl implements ResultSetReturn { @Override public int executeUpdate(PreparedStatement statement) { - long executeStart = 0; + long executeStartNanos = 0; if ( this.sqlStatementLogger.getLogSlowQuery() > 0 ) { - executeStart = System.currentTimeMillis(); + executeStartNanos = System.nanoTime(); } try { jdbcExecuteStatementStart(); @@ -201,16 +201,16 @@ public class ResultSetReturnImpl implements ResultSetReturn { } finally { jdbcExecuteStatementEnd(); - sqlStatementLogger.logSlowQuery( statement, executeStart ); + sqlStatementLogger.logSlowQuery( statement, executeStartNanos ); } } @Override public int executeUpdate(Statement statement, String sql) { sqlStatementLogger.logStatement( sql ); - long executeStart = 0; + long executeStartNanos = 0; if ( this.sqlStatementLogger.getLogSlowQuery() > 0 ) { - executeStart = System.currentTimeMillis(); + executeStartNanos = System.nanoTime(); } try { jdbcExecuteStatementStart(); @@ -221,7 +221,7 @@ public class ResultSetReturnImpl implements ResultSetReturn { } finally { jdbcExecuteStatementEnd(); - sqlStatementLogger.logSlowQuery( statement, executeStart ); + sqlStatementLogger.logSlowQuery( statement, executeStartNanos ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/SqlStatementLogger.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/SqlStatementLogger.java index 36444183fd..2c4515bdfc 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/SqlStatementLogger.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/SqlStatementLogger.java @@ -7,6 +7,7 @@ package org.hibernate.engine.jdbc.spi; import java.sql.Statement; +import java.util.concurrent.TimeUnit; import org.hibernate.engine.jdbc.internal.FormatStyle; import org.hibernate.engine.jdbc.internal.Formatter; @@ -134,34 +135,34 @@ public class SqlStatementLogger { * Log a slow SQL query * * @param statement SQL statement. - * @param startTime Start time in milliseconds. + * @param startTimeNanos Start time in nanoseconds. */ - public void logSlowQuery(Statement statement, long startTime) { + public void logSlowQuery(Statement statement, long startTimeNanos) { if ( logSlowQuery < 1 ) { return; } - logSlowQuery( statement.toString(), startTime ); + logSlowQuery( statement.toString(), startTimeNanos ); } /** * Log a slow SQL query * * @param sql The SQL query. - * @param startTime Start time in milliseconds. + * @param startTimeNanos Start time in nanoseconds. */ @AllowSysOut - public void logSlowQuery(String sql, long startTime) { + public void logSlowQuery(String sql, long startTimeNanos) { if ( logSlowQuery < 1 ) { return; } - assert startTime > 0 : "startTime is invalid!"; + if ( startTimeNanos <= 0 ) { + throw new IllegalArgumentException( "startTimeNanos should be greater than 0!" ); + } - long spent = System.currentTimeMillis() - startTime; + long queryExecutionMillis = TimeUnit.NANOSECONDS.toMillis( System.nanoTime() - startTimeNanos ); - assert spent >= 0 : "startTime is invalid!"; - - if ( spent > logSlowQuery ) { - String logData = "SlowQuery: " + spent + " milliseconds. SQL: '" + sql + "'"; + if ( queryExecutionMillis > logSlowQuery ) { + String logData = "SlowQuery: " + queryExecutionMillis + " milliseconds. SQL: '" + sql + "'"; LOG_SLOW.info( logData ); if ( logToStdout ) { System.out.println( logData ); From c76675f11ac47dac77cb8109873ce7adf913231e Mon Sep 17 00:00:00 2001 From: Vlad Mihalcea Date: Tue, 25 Feb 2020 18:29:43 +0200 Subject: [PATCH 18/27] Changes according to PR review. Requires rebase! --- .../java/org/hibernate/engine/jdbc/spi/SqlStatementLogger.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/SqlStatementLogger.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/SqlStatementLogger.java index 2c4515bdfc..4c5e9c9746 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/SqlStatementLogger.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/SqlStatementLogger.java @@ -156,7 +156,7 @@ public class SqlStatementLogger { return; } if ( startTimeNanos <= 0 ) { - throw new IllegalArgumentException( "startTimeNanos should be greater than 0!" ); + throw new IllegalArgumentException( "startTimeNanos [" + startTimeNanos + "] should be greater than 0!" ); } long queryExecutionMillis = TimeUnit.NANOSECONDS.toMillis( System.nanoTime() - startTimeNanos ); From fe52328ab66155a79da8698cbad5419cd32feb19 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Mon, 24 Feb 2020 14:23:06 +0000 Subject: [PATCH 19/27] HHH-13878 Remove final from SessionImpl#internalLoad In Hibernate RX we need to override this method. --- .../src/main/java/org/hibernate/internal/SessionImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 56d8051070..de65456798 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -1005,7 +1005,7 @@ public class SessionImpl } @Override - public final Object internalLoad( + public Object internalLoad( String entityName, Serializable id, boolean eager, From 4e58006f10cd3adb662eb4c770968b98021c7c28 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Mon, 24 Feb 2020 14:23:46 +0000 Subject: [PATCH 20/27] HHH-13878 Make some private methods in Loader protected This way we can reduce the amount of copy and paste in Hibernate Rx. --- .../src/main/java/org/hibernate/loader/Loader.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/loader/Loader.java b/hibernate-core/src/main/java/org/hibernate/loader/Loader.java index 90db371a9d..6afa106f52 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/Loader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/Loader.java @@ -676,7 +676,7 @@ public abstract class Loader { } } - private static EntityKey getOptionalObjectKey(QueryParameters queryParameters, SharedSessionContractImplementor session) { + protected static EntityKey getOptionalObjectKey(QueryParameters queryParameters, SharedSessionContractImplementor session) { final Object optionalObject = queryParameters.getOptionalObject(); final Serializable optionalId = queryParameters.getOptionalId(); final String optionalEntityName = queryParameters.getOptionalEntityName(); @@ -1237,7 +1237,7 @@ public abstract class Loader { } } - private void endCollectionLoad( + protected void endCollectionLoad( final Object resultSetId, final SharedSessionContractImplementor session, final CollectionPersister collectionPersister) { @@ -1450,7 +1450,7 @@ public abstract class Loader { * is being initialized, to account for the possibility of the collection having * no elements (hence no rows in the result set). */ - private void handleEmptyCollections( + protected void handleEmptyCollections( final Serializable[] keys, final Object resultSetId, final SharedSessionContractImplementor session) { @@ -1637,7 +1637,7 @@ public abstract class Loader { /** * The entity instance is already in the session cache */ - private void instanceAlreadyLoaded( + protected void instanceAlreadyLoaded( final ResultSet rs, final int i, final Loadable persister, @@ -1707,7 +1707,7 @@ public abstract class Loader { /** * The entity instance is not in the session cache */ - private Object instanceNotYetLoaded( + protected Object instanceNotYetLoaded( final ResultSet rs, final int i, final Loadable persister, From 90c669108be39196bfbecdf94b8d2d5af4a659e1 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Wed, 26 Feb 2020 09:40:53 +0000 Subject: [PATCH 21/27] HHH-13876 Remove method Stack#getPrevious as its unused and contains a bug --- .../internal/util/collections/SingletonStack.java | 5 ----- .../org/hibernate/internal/util/collections/Stack.java | 5 ----- .../internal/util/collections/StandardStack.java | 8 -------- 3 files changed, 18 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/SingletonStack.java b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/SingletonStack.java index 3e59bc5a37..f1c0a00b04 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/SingletonStack.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/SingletonStack.java @@ -34,11 +34,6 @@ public class SingletonStack implements Stack { return instance; } - @Override - public T getPrevious() { - return null; - } - @Override public int depth() { return 1; diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/Stack.java b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/Stack.java index 35048585b3..96c1aa4b02 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/Stack.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/Stack.java @@ -32,11 +32,6 @@ public interface Stack { */ T getCurrent(); - /** - * The element previously at the top of the stack before the current one - */ - T getPrevious(); - /** * How many elements are currently on the stack? */ diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/StandardStack.java b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/StandardStack.java index b2025c7c36..0d4598b952 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/StandardStack.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/StandardStack.java @@ -42,14 +42,6 @@ public class StandardStack implements Stack { return internalStack.peek(); } - @Override - public T getPrevious() { - if ( internalStack.size() < 2 ) { - return null; - } - return internalStack.get( internalStack.size() - 2 ); - } - @Override public int depth() { return internalStack.size(); From e65ef1354c6eb015f9cf361c623b2a74a253d853 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Wed, 26 Feb 2020 09:41:30 +0000 Subject: [PATCH 22/27] HHH-13876 Delete the immutable SingletonStack implementation as its no longer used --- .../util/collections/SingletonStack.java | 60 ------------------- 1 file changed, 60 deletions(-) delete mode 100644 hibernate-core/src/main/java/org/hibernate/internal/util/collections/SingletonStack.java diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/SingletonStack.java b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/SingletonStack.java deleted file mode 100644 index f1c0a00b04..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/SingletonStack.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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.internal.util.collections; - -import java.util.function.Consumer; -import java.util.function.Function; - -/** - * @author Steve Ebersole - */ -public class SingletonStack implements Stack { - private final T instance; - - public SingletonStack(T instance) { - this.instance = instance; - } - - @Override - public void push(T newCurrent) { - throw new UnsupportedOperationException( "Cannot push to a singleton Stack" ); - } - - @Override - public T pop() { - throw new UnsupportedOperationException( "Cannot pop from a singleton Stack" ); - } - - @Override - public T getCurrent() { - return instance; - } - - @Override - public int depth() { - return 1; - } - - @Override - public boolean isEmpty() { - return false; - } - - @Override - public void clear() { - } - - @Override - public void visitCurrentFirst(Consumer action) { - action.accept( instance ); - } - - @Override - public X findCurrentFirst(Function action) { - return action.apply( instance ); - } -} From b856d534cba88064da87f27010cad30e0bdb62f5 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Wed, 26 Feb 2020 09:45:55 +0000 Subject: [PATCH 23/27] HHH-13876 Remove unused code from StandardStack --- .../internal/util/collections/Stack.java | 13 ---------- .../util/collections/StandardStack.java | 24 +++---------------- 2 files changed, 3 insertions(+), 34 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/Stack.java b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/Stack.java index 96c1aa4b02..1c49206138 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/Stack.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/Stack.java @@ -6,9 +6,6 @@ */ package org.hibernate.internal.util.collections; -import java.util.function.Consumer; -import java.util.function.Function; - /** * Stack implementation exposing useful methods for Hibernate needs. * @@ -47,14 +44,4 @@ public interface Stack { */ void clear(); - /** - * Visit all elements in the stack, starting with the current and working back - */ - void visitCurrentFirst(Consumer action); - - /** - * Find an element on the stack and return a value. The first non-null element - * returned from `action` stops the iteration and is returned from here - */ - X findCurrentFirst(Function action); } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/StandardStack.java b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/StandardStack.java index 0d4598b952..db11ba4e5e 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/StandardStack.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/StandardStack.java @@ -17,16 +17,13 @@ import java.util.function.Function; * * @author Steve Ebersole */ -public class StandardStack implements Stack { - private LinkedList internalStack = new LinkedList<>(); +public final class StandardStack implements Stack { + + private final LinkedList internalStack = new LinkedList<>(); public StandardStack() { } - public StandardStack(T initial) { - internalStack.add( initial ); - } - @Override public void push(T newCurrent) { internalStack.addFirst( newCurrent ); @@ -57,19 +54,4 @@ public class StandardStack implements Stack { internalStack.clear(); } - @Override - public void visitCurrentFirst(Consumer action) { - internalStack.forEach( action ); - } - - @Override - public X findCurrentFirst(Function function) { - for ( T t : internalStack ) { - final X result = function.apply( t ); - if ( result != null ) { - return result; - } - } - return null; - } } From c7e551afe704733eaf9b9050413234d286a3282a Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Fri, 28 Feb 2020 16:04:07 +0000 Subject: [PATCH 24/27] Add a Test Case Guide adoc --- test-case-guide.adoc | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 test-case-guide.adoc diff --git a/test-case-guide.adoc b/test-case-guide.adoc new file mode 100644 index 0000000000..2a836abb8b --- /dev/null +++ b/test-case-guide.adoc @@ -0,0 +1,32 @@ += Test Case Guide +:toc: + +This is meant as a guide for writing test cases to be attached to bug reports in the Hibernate Jira. Really most of the information here works just as well when asking for help on community help channels (forums, IRC, HipChat, etc). + + +== Write a good test + +There are a number of tenants that make up a good test case as opposed to a poor one. In fact there are a few guides for this across the web including (http://stackoverflow.com/help/mcve[MCVE]) and (http://sscce.org/[SSCCE]). These guides all assert the same ideas albeit using different terms. Given the ubiquity of StackOverflow and the fact that the MCVE guidelines were written specifically for StackOverflow, we will use those terms here as we assume most developers have seen them before: + +* (M)inimal - Provide just the minimal information needed. If second level caching is irrelevant to the bug report then the test should not use second level caching. If entity inheritance is irrelevant then do not use it in the test. If your application uses Spring Data, remove Spring Data from the test. +* (C)omplete - Provide all information needed to reproduce the problem. If a bug only occurs when using bytecode enhancement, then the test should include bytecode enhancement. In other words the test should be self-contained. +* (V)erifiable - The test should actually reproduce the problem being reported. + + +== Test templates + +The Hibernate team maintains a set of "test templates" intended to help developers write tests. These test templates are maintained in GitHub @ https://github.com/hibernate/hibernate-test-case-templates/tree/master/orm[hibernate-test-case-templates] + +* If you want to use the Hibernate native API, you should follow the instructions from http://in.relation.to/2015/06/26/hibernate-test-case-templates/[this article]. +* If you want to use JPA, you should use the JPA templates that were detailed in http://in.relation.to/2016/01/14/hibernate-jpa-test-case-template/[this article]. + +NOTE: the test templates are generally not a good starting point for problems building the SessionFactory/EntityManager. In JUnit terms they manage the SessionFactory/EntityManager as set-up and teardown constructs._ + +== Annotations + +When using "test templates" you can annotate a single test or a whole test class with one of the following annotations: + +* FailureExpected - allows to skip a single test or all test of a class, because test failures are expected. The test will acutally run, but not lead to an error report. In fact if a test is marked with `@FailureExpected` and the test actually succeed an error occurs. As a parameters to this annotation a jira key is required. +* RequiresDialect - tests methods/classes annotated with `@RequiresDialect` will only run if the current Dialect is matching the one specified on as annotation parameter. You can also specify a comment and/or jira key explaining why this test requires a certain dialect +* RequiresDialectFeature - tests methods/classes annotated with `@RequiresDialectFeature` will only run if the current Dialect offers the specified feature. Examples for this features are `SupportsSequences`, `SupportsExpectedLobUsagePattern` or `SupportsIdentityColumn`s. You can add more feature if you need to. Have a look at `DialectChecks`. +* SkipForDialect - tests methods/classes annotated with `@SkipForDialect` will not run if the current Dialect is matching the one specified on as annotation parameter. You can also specify a comment and/or jira key explaining why this test has to be skipped for the Dialect. From 2bda2d1fd28d1ed2904f11f82a5be7f4dc884485 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Fri, 28 Feb 2020 16:47:16 +0000 Subject: [PATCH 25/27] Fix a Test Case Guide adoc error --- test-case-guide.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-case-guide.adoc b/test-case-guide.adoc index 2a836abb8b..4e23f321b0 100644 --- a/test-case-guide.adoc +++ b/test-case-guide.adoc @@ -28,5 +28,5 @@ When using "test templates" you can annotate a single test or a whole test clas * FailureExpected - allows to skip a single test or all test of a class, because test failures are expected. The test will acutally run, but not lead to an error report. In fact if a test is marked with `@FailureExpected` and the test actually succeed an error occurs. As a parameters to this annotation a jira key is required. * RequiresDialect - tests methods/classes annotated with `@RequiresDialect` will only run if the current Dialect is matching the one specified on as annotation parameter. You can also specify a comment and/or jira key explaining why this test requires a certain dialect -* RequiresDialectFeature - tests methods/classes annotated with `@RequiresDialectFeature` will only run if the current Dialect offers the specified feature. Examples for this features are `SupportsSequences`, `SupportsExpectedLobUsagePattern` or `SupportsIdentityColumn`s. You can add more feature if you need to. Have a look at `DialectChecks`. +* RequiresDialectFeature - tests methods/classes annotated with `@RequiresDialectFeature` will only run if the current Dialect offers the specified feature. Examples for this features are `SupportsSequences`, `SupportsExpectedLobUsagePattern` or `SupportsIdentityColumns`. You can add more feature if you need to. Have a look at `DialectChecks`. * SkipForDialect - tests methods/classes annotated with `@SkipForDialect` will not run if the current Dialect is matching the one specified on as annotation parameter. You can also specify a comment and/or jira key explaining why this test has to be skipped for the Dialect. From 188c05cc333dec0fe3edfefd8f27fe19b63c702a Mon Sep 17 00:00:00 2001 From: Nathan Xu Date: Thu, 27 Feb 2020 10:21:19 -0500 Subject: [PATCH 26/27] HHH-13322 Fix Oracle dialect's 'getQuerySequencesString()' issue by limiting to current schema --- .../hibernate/dialect/Oracle8iDialect.java | 4 +- .../OracleExtractSequenceMatadataTest.java | 133 ++++++++++++++++++ 2 files changed, 135 insertions(+), 2 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/dialect/functional/OracleExtractSequenceMatadataTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/Oracle8iDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/Oracle8iDialect.java index 5f0c25bac2..b74e80eea3 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Oracle8iDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Oracle8iDialect.java @@ -420,7 +420,7 @@ public class Oracle8iDialect extends Dialect { else { return String.format( - "%s start with %d increment by %d", + "%s start with %d increment by %d", getCreateSequenceString( sequenceName ), initialValue, incrementSize @@ -490,7 +490,7 @@ public class Oracle8iDialect extends Dialect { @Override public String getQuerySequencesString() { - return "select * from all_sequences"; + return "select * from user_sequences"; } public SequenceInformationExtractor getSequenceInformationExtractor() { diff --git a/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/OracleExtractSequenceMatadataTest.java b/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/OracleExtractSequenceMatadataTest.java new file mode 100644 index 0000000000..8ea1c38cfb --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/OracleExtractSequenceMatadataTest.java @@ -0,0 +1,133 @@ +package org.hibernate.test.dialect.functional; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Statement; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +import org.hibernate.annotations.GenericGenerator; +import org.hibernate.annotations.Parameter; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; +import org.hibernate.cfg.Environment; +import org.hibernate.dialect.Oracle8iDialect; +import org.hibernate.id.SequenceMismatchStrategy; +import org.hibernate.id.enhanced.SequenceStyleGenerator; + +import org.hibernate.testing.BeforeClassOnce; +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.test.annotations.lob.OracleSeqIdGenDialect; +import org.junit.AfterClass; +import org.junit.Test; + +/** + * @author Nathan Xu + */ +@TestForIssue( jiraKey = "HHH-13322" ) +@RequiresDialect( Oracle8iDialect.class ) +public class OracleExtractSequenceMatadataTest extends BaseCoreFunctionalTestCase { + + private static final String SEQUENCE_NAME = SequenceStyleGenerator.DEF_SEQUENCE_NAME; + private static final String SEQUENCE_INCREMENT_SIZE = "50"; + + private static final String OTHER_SCHEMA_NAME = "C##HHH13322"; // 'C##' is common custom user name prefix in Oracle + private static final String SEQUENCE_INCREMENT_SIZE_FROM_OTHER_SCHEMA = "1"; + + @Override + protected void configure(Configuration configuration) { + configuration.setProperty( Environment.DIALECT, OracleSeqIdGenDialect.class.getName() ); + configuration.setProperty( + AvailableSettings.SEQUENCE_INCREMENT_SIZE_MISMATCH_STRATEGY, + SequenceMismatchStrategy.EXCEPTION.toString() ); + } + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { TestEntity.class }; + } + + @BeforeClassOnce + public static void setUpDB() throws Exception { + + assert ! SEQUENCE_INCREMENT_SIZE.equals( SEQUENCE_INCREMENT_SIZE_FROM_OTHER_SCHEMA ); + + try ( Connection conn = getConnection() ) { + try ( Statement stmt = conn.createStatement() ) { + try { + stmt.execute( String.format( "DROP USER %s CASCADE", OTHER_SCHEMA_NAME ) ); + } + catch ( Exception ignore ) { + } + + stmt.execute( String.format( "CREATE USER %s IDENTIFIED BY whatever", OTHER_SCHEMA_NAME ) ); + + // create identical sequence in other schema with different 'increment size' than specified in id field of the entity + stmt.execute( String.format( "CREATE SEQUENCE %s.%s START WITH 1 INCREMENT BY %s", + OTHER_SCHEMA_NAME, + SEQUENCE_NAME, + SEQUENCE_INCREMENT_SIZE_FROM_OTHER_SCHEMA + ) ); + + // ensure no sequence exists for current schema, so the identical sequence in the other schema could be the only culprit + try { + stmt.execute( String.format( + "DROP SEQUENCE %s", + SEQUENCE_NAME + ) ); + } + catch ( Exception ignore ) { + } + } + } + } + + @Test + public void testHibernateLaunchSuccessfully() { + // if we were lucky to reach here, woa, the sequence from other schema didn't bother us (with different 'increment size's) + } + + @AfterClass + public static void tearDownDB() throws SQLException { + try ( Connection conn = getConnection() ) { + try ( Statement stmt = conn.createStatement() ) { + // dropping user with 'cascade' will drop the sequence as well + stmt.execute( String.format( "DROP USER %s CASCADE", OTHER_SCHEMA_NAME ) ); + } + catch ( Exception ignore ) { + } + } + } + + private static Connection getConnection() throws SQLException { + String url = Environment.getProperties().getProperty( Environment.URL ); + String user = Environment.getProperties().getProperty( Environment.USER ); + String password = Environment.getProperties().getProperty( Environment.PASS ); + return DriverManager.getConnection( url, user, password ); + } + + @Entity(name = "TestEntity") + public static class TestEntity { + + @Id + @GeneratedValue( strategy = GenerationType.SEQUENCE, generator = "sequence_generator" ) + @GenericGenerator( + name = "sequence_generator", + strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator", + parameters = { + @Parameter(name = "sequence_name", value = SEQUENCE_NAME), + @Parameter(name = "initial_value", value = "1" ), + @Parameter(name = "increment_size", value = SEQUENCE_INCREMENT_SIZE), + @Parameter(name = "optimizer", value = "pooled") + } + ) + private Long id; + + } + +} From 5bf772c59d017ebe3674d4cd9f24d5e72f0b07d6 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Mon, 2 Mar 2020 11:48:54 +0000 Subject: [PATCH 27/27] HHH-13322 Fix test --- ...tadataTest.java => OracleExtractSequenceMetadataTest.java} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename hibernate-core/src/test/java/org/hibernate/test/dialect/functional/{OracleExtractSequenceMatadataTest.java => OracleExtractSequenceMetadataTest.java} (96%) diff --git a/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/OracleExtractSequenceMatadataTest.java b/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/OracleExtractSequenceMetadataTest.java similarity index 96% rename from hibernate-core/src/test/java/org/hibernate/test/dialect/functional/OracleExtractSequenceMatadataTest.java rename to hibernate-core/src/test/java/org/hibernate/test/dialect/functional/OracleExtractSequenceMetadataTest.java index 8ea1c38cfb..00df3bff6f 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/OracleExtractSequenceMatadataTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/dialect/functional/OracleExtractSequenceMetadataTest.java @@ -31,12 +31,12 @@ import org.junit.Test; */ @TestForIssue( jiraKey = "HHH-13322" ) @RequiresDialect( Oracle8iDialect.class ) -public class OracleExtractSequenceMatadataTest extends BaseCoreFunctionalTestCase { +public class OracleExtractSequenceMetadataTest extends BaseCoreFunctionalTestCase { private static final String SEQUENCE_NAME = SequenceStyleGenerator.DEF_SEQUENCE_NAME; private static final String SEQUENCE_INCREMENT_SIZE = "50"; - private static final String OTHER_SCHEMA_NAME = "C##HHH13322"; // 'C##' is common custom user name prefix in Oracle + private static final String OTHER_SCHEMA_NAME = "hibernate_orm_test_2"; private static final String SEQUENCE_INCREMENT_SIZE_FROM_OTHER_SCHEMA = "1"; @Override