From 903d542020ea2a22e796ef8c21b7f55c26105967 Mon Sep 17 00:00:00 2001 From: Vlad Mihalcea Date: Thu, 15 Nov 2018 09:40:23 +0200 Subject: [PATCH] HHH-13105 - Add registries for user-defined Types as currently a custom Type is instantiated and configured 9 times --- .../java/org/hibernate/type/TypeFactory.java | 65 ++++++++++++-- .../hibernate/type/spi/TypeConfiguration.java | 1 + .../test/type/contributor/ArraySpyType.java | 44 ++++++++++ .../CustomTypeInstanceCountTest.java | 87 +++++++++++++++++++ 4 files changed, 188 insertions(+), 9 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/type/contributor/ArraySpyType.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/type/contributor/CustomTypeInstanceCountTest.java 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 f18866cb69..923eedd0a0 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/TypeFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/type/TypeFactory.java @@ -8,11 +8,16 @@ package org.hibernate.type; import java.io.Serializable; import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; import java.util.Properties; import org.hibernate.MappingException; +import org.hibernate.SessionFactory; +import org.hibernate.SessionFactoryObserver; import org.hibernate.classic.Lifecycle; -import org.hibernate.engine.spi.SessionFactoryImplementor; + import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.ReflectHelper; import org.hibernate.tuple.component.ComponentMetamodel; @@ -38,7 +43,7 @@ import static org.hibernate.internal.CoreLogging.messageLogger; */ @Deprecated @SuppressWarnings({"unchecked"}) -public final class TypeFactory implements Serializable { +public final class TypeFactory implements Serializable, SessionFactoryObserver { private static final CoreMessageLogger LOG = messageLogger( TypeFactory.class ); /** @@ -52,35 +57,47 @@ public final class TypeFactory implements Serializable { private final TypeConfiguration typeConfiguration; private final TypeScope typeScope; + private final Map typeRegistry = new HashMap<>(); + private final Map compositeTypeRegistry = new HashMap<>(); + private final Map customTypeRegistry = new HashMap<>(); + private final Map entityTypeRegistry = new HashMap<>(); + private final Map serializableTypeRegistry = new HashMap<>(); + public TypeFactory(TypeConfiguration typeConfiguration) { this.typeConfiguration = typeConfiguration; this.typeScope = (TypeScope) () -> typeConfiguration; } - public SessionFactoryImplementor resolveSessionFactory() { - return typeConfiguration.getSessionFactory(); + @Override + public void sessionFactoryCreated(SessionFactory factory) { + typeRegistry.clear(); + compositeTypeRegistry.clear(); + customTypeRegistry.clear(); + entityTypeRegistry.clear(); + serializableTypeRegistry.clear(); } public Type byClass(Class clazz, Properties parameters) { + TypeKey typeKey = new TypeKey( clazz, parameters ); if ( Type.class.isAssignableFrom( clazz ) ) { - return type( clazz, parameters ); + return typeRegistry.computeIfAbsent( typeKey, key -> type( clazz, parameters ) ); } if ( CompositeUserType.class.isAssignableFrom( clazz ) ) { - return customComponent( clazz, parameters ); + return compositeTypeRegistry.computeIfAbsent( typeKey, key -> customComponent( clazz, parameters ) ); } if ( UserType.class.isAssignableFrom( clazz ) ) { - return custom( clazz, parameters ); + return customTypeRegistry.computeIfAbsent( typeKey, key -> custom( clazz, parameters ) ); } if ( Lifecycle.class.isAssignableFrom( clazz ) ) { // not really a many-to-one association *necessarily* - return manyToOne( clazz.getName() ); + return entityTypeRegistry.computeIfAbsent( typeKey, key -> manyToOne( clazz.getName() ) ); } if ( Serializable.class.isAssignableFrom( clazz ) ) { - return serializable( clazz ); + return serializableTypeRegistry.computeIfAbsent( typeKey, key -> serializable( clazz ) ); } return null; @@ -404,4 +421,34 @@ public final class TypeFactory implements Serializable { public Type any(Type metaType, Type identifierType) { return new AnyType( typeScope, metaType, identifierType ); } + + private static class TypeKey { + private final Class clazz; + private final Properties parameters = new Properties(); + + private TypeKey(Class clazz, Properties parameters) { + this.clazz = clazz; + if ( parameters != null && !parameters.isEmpty() ) { + this.parameters.putAll( parameters ); + } + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + TypeKey typeKey = (TypeKey) o; + return Objects.equals( clazz, typeKey.clazz ) && + Objects.equals( parameters, typeKey.parameters ); + } + + @Override + public int hashCode() { + return Objects.hash( clazz, parameters ); + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/spi/TypeConfiguration.java b/hibernate-core/src/main/java/org/hibernate/type/spi/TypeConfiguration.java index 5427c8f587..c9d85787fe 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/spi/TypeConfiguration.java +++ b/hibernate-core/src/main/java/org/hibernate/type/spi/TypeConfiguration.java @@ -166,6 +166,7 @@ public class TypeConfiguration implements SessionFactoryObserver, Serializable { scope.setSessionFactory( sessionFactory ); sessionFactory.addObserver( this ); + sessionFactory.addObserver( this.typeFactory ); return new MetamodelImpl( sessionFactory, this ); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/contributor/ArraySpyType.java b/hibernate-core/src/test/java/org/hibernate/test/type/contributor/ArraySpyType.java new file mode 100644 index 0000000000..b14c99e3bb --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/type/contributor/ArraySpyType.java @@ -0,0 +1,44 @@ +package org.hibernate.test.type.contributor; + +import org.hibernate.dialect.Dialect; +import org.hibernate.type.AbstractSingleColumnStandardBasicType; +import org.hibernate.type.DiscriminatorType; +import org.hibernate.type.descriptor.sql.VarcharTypeDescriptor; + +/** + * @author Vlad Mihalcea + */ +public class ArraySpyType + extends AbstractSingleColumnStandardBasicType + implements DiscriminatorType { + + private static int instanceCount; + + public ArraySpyType() { + super( VarcharTypeDescriptor.INSTANCE, ArrayTypeDescriptor.INSTANCE ); + instanceCount++; + } + + @Override + public Array stringToObject(String xml) throws Exception { + return fromString( xml ); + } + + @Override + public String objectToSQLString(Array value, Dialect dialect) throws Exception { + return toString( value ); + } + + @Override + public String getName() { + return "comma-separated-array"; + } + + public static int getInstanceCount() { + return instanceCount; + } + + public static void resetInstanceCount() { + instanceCount = 0; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/type/contributor/CustomTypeInstanceCountTest.java b/hibernate-core/src/test/java/org/hibernate/test/type/contributor/CustomTypeInstanceCountTest.java new file mode 100644 index 0000000000..8159cfbf14 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/type/contributor/CustomTypeInstanceCountTest.java @@ -0,0 +1,87 @@ +/* + * 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 javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.annotations.Type; +import org.hibernate.annotations.TypeDef; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * @author Vlad Mihalcea + */ +@TestForIssue( jiraKey = "HHH-13105" ) +public class CustomTypeInstanceCountTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { CorporateUser.class }; + } + + @Override + public void buildEntityManagerFactory() { + ArraySpyType.resetInstanceCount(); + assertEquals( 0, ArraySpyType.getInstanceCount() ); + super.buildEntityManagerFactory(); + } + + @Test + public void test() { + 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" ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + List users = entityManager.createQuery( + "select u from CorporateUser u", CorporateUser.class ) + .getResultList(); + + assertEquals( 1, users.size() ); + } ); + + assertEquals( 1, ArraySpyType.getInstanceCount() ); + } + + @Entity(name = "CorporateUser") + @TypeDef(typeClass = ArraySpyType.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; + } + } + +} +