HHH-13105 - Add registries for user-defined Types as currently a custom Type is instantiated and configured 9 times

This commit is contained in:
Vlad Mihalcea 2018-11-15 09:40:23 +02:00
parent dab50a6d8a
commit 903d542020
4 changed files with 188 additions and 9 deletions

View File

@ -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<TypeKey, Type> typeRegistry = new HashMap<>();
private final Map<TypeKey, CompositeCustomType> compositeTypeRegistry = new HashMap<>();
private final Map<TypeKey, CustomType> customTypeRegistry = new HashMap<>();
private final Map<TypeKey, EntityType> entityTypeRegistry = new HashMap<>();
private final Map<TypeKey, SerializableType> 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 );
}
}
}

View File

@ -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 );
}

View File

@ -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<Array>
implements DiscriminatorType<Array> {
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;
}
}

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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<CorporateUser> 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;
}
}
}