Custom UserType is not really a singleton

When using a custom UserType, why does Hibernate create many of them per class/field, instead of having a singleton?

For example:

public class A {
    @Type("MyType") private String a1;
    @Type("MyType") private String a2;
}
public class B {
    @Type("MyType") private String b1;
    @Type("MyType") private String b2;
}

I would expect Hibernate to generate 4 MyType singletons, but on startup, logging shows that setParameterValues method is called many times on unique MyType instances.

If I get the EntityPersister.getPropertyType().getUserType(), I get one of the MyType instances created, but it’s not the only one. When nullSafeGet/Set are called it can be from one of several instances associated with the specific Java field.

Why is MyType a singleton for each field? Is it possible to find all the instances of MyType for each field?

I’m on Hibernate 5.4.19.Final.

I guess this is because a UserType instance can be parameterized by the type use.

That doesn’t quite explain it; each class/entity field needs to definitely get its own singleton, so in my example there should be at least 4 MyType singletons: a1, a2, b1, and b2. That allows parameterizing each one separately.

But Hibernate instead creates several instances each. This is counter-intuitive to the Hibernate docs which imply that these are singletons (e.g. the examples register types using an INSTANCE singleton).

Is the amount of instances increasing or does it stay constant? If you can post stacktraces of the constructor calls I can give it a deeper look. I understand that it would be an issue if the amount is increasing, but if not, why is it so important to you that the instances are singletons?

The issue is that I want to inject different Spring beans into each MyType instance: a1 gets one value, but a2/b1/b2 all get different values. These are all injected after the EntityManagerFactory is complete and Spring is initializing the rest of the application beans, by crawling the EMF/EntityPersister.

If MyTypes are singletons per field, I can easily find them all and inject whatever I want. But EntityPersister.getPropertyType(“a1”).getUserType() returns only one of the instances created for a given field. I don’t see a way to find all of them, or to set the configuration only once for the MyType associated with a given field.

Apparently the problem is that SimpleValue.getType always re-creates the type; it’s own type variable isn’t being assigned, so every call to getType() ends up calling TypeResolver.heuristicType.

Here are the first few stack traces on the MyType constructor; these are all for the same field:

	at java.lang.Class.newInstance(Class.java:442)
	at org.hibernate.type.TypeFactory.custom(TypeFactory.java:177)
	at org.hibernate.type.TypeFactory.byClass(TypeFactory.java:78)
	at org.hibernate.type.TypeResolver.heuristicType(TypeResolver.java:126)
	at org.hibernate.mapping.SimpleValue.getType(SimpleValue.java:484)
	at org.hibernate.mapping.SimpleValue.isValid(SimpleValue.java:466)
	at org.hibernate.mapping.Property.isValid(Property.java:227)
	at org.hibernate.mapping.PersistentClass.validate(PersistentClass.java:624)
	at org.hibernate.mapping.RootClass.validate(RootClass.java:267)
	at org.hibernate.boot.internal.MetadataImpl.validate(MetadataImpl.java:354)
	at org.hibernate.boot.internal.SessionFactoryBuilderImpl.build(SessionFactoryBuilderImpl.java:465)
	at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:1259)

Then again:

	at java.lang.Class.newInstance(Class.java:442)
	at org.hibernate.type.TypeFactory.custom(TypeFactory.java:177)
	at org.hibernate.type.TypeFactory.byClass(TypeFactory.java:78)
	at org.hibernate.type.TypeResolver.heuristicType(TypeResolver.java:126)
	at org.hibernate.mapping.SimpleValue.getType(SimpleValue.java:484)
	at org.hibernate.mapping.Property.getType(Property.java:70)
	at org.hibernate.event.service.internal.EventListenerRegistryImpl.prepare(EventListenerRegistryImpl.java:152)
	at org.hibernate.boot.internal.MetadataImpl.initSessionFactory(MetadataImpl.java:379)
	at org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:213)
	at org.hibernate.boot.internal.SessionFactoryBuilderImpl.build(SessionFactoryBuilderImpl.java:469)
	at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:1259)

Again:

	at java.lang.Class.newInstance(Class.java:442)
	at org.hibernate.type.TypeFactory.custom(TypeFactory.java:177)
	at org.hibernate.type.TypeFactory.byClass(TypeFactory.java:78)
	at org.hibernate.type.TypeResolver.heuristicType(TypeResolver.java:126)
	at org.hibernate.mapping.SimpleValue.getType(SimpleValue.java:484)
	at org.hibernate.tuple.PropertyFactory.buildEntityBasedAttribute(PropertyFactory.java:158)
	at org.hibernate.tuple.entity.EntityMetamodel.<init>(EntityMetamodel.java:224)
	at org.hibernate.persister.entity.AbstractEntityPersister.<init>(AbstractEntityPersister.java:604)
	at org.hibernate.persister.entity.SingleTableEntityPersister.<init>(SingleTableEntityPersister.java:125)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
	at org.hibernate.persister.internal.PersisterFactoryImpl.createEntityPersister(PersisterFactoryImpl.java:96)
	at org.hibernate.persister.internal.PersisterFactoryImpl.createEntityPersister(PersisterFactoryImpl.java:77)
	at org.hibernate.metamodel.internal.MetamodelImpl.initialize(MetamodelImpl.java:181)
	at org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:301)
	at org.hibernate.boot.internal.SessionFactoryBuilderImpl.build(SessionFactoryBuilderImpl.java:469)
	at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:1259)

There’s a few more but you get the idea.

Thanks for the report, I’ll see what I can do. I created the following issue for this: https://hibernate.atlassian.net/browse/HHH-14335

Thank you! I do have a workaround for now; I implement DynamicParameterizedType and the entity class and field name are in the parameters, so I centralize the configuration using a static map. Not ideal, but it gets me through.