HHH-14198 - Expose CompositeUserTypes through JPA Metamodel

Composite User Types work like regular Composite Types (like Embeddable) in HQL. However, because they cannot be represented in the JPA metamodel, libraries like [GraphQL for JPA](https://github.com/jcrygier/graphql-jpa) or [Blaze-Persistence](https://persistence.blazebit.com/) cannot fully utilize them. In order to make the composite property names available to these libraries, it would be nice to optionally expose these attributes as embedded attributes. This pull request aims to make that change and makes it configurable through a custom setting.

Composite User Types are a common solution for mapping composite interfaces. A common example is for example `Money` from the Java Money API (JSR-354), for which composite user types are implemented in [Jadira](http://jadira.sourceforge.net/usertype-userguide.html).

I know Composite User Types are currently not consiered in Hibernate 6.x. See also [this](https://hibernate.zulipchat.com/#narrow/stream/132094-hibernate-orm-dev/topic/CompositeUserType) Zulip thread. I am not sure if Hibernate 6.x will even have multi column types, which I presume would be a requirement to even introduce Composite User types back at some point. Usually Embeddables are a much easier, suitable mechanism for composite user types. But Embeddables are not always a viable alternative, because Embeddables require the type to be subclassed (as an interface cannot be mapped, and the type may not solely comprise fields that can be mapped to a simple basic type). To deal with this exact problem, `MonetaryAmounts` are still mapped as composite user type. There also have been suggestions to the JPA Spec to consider `AttributeConverters` for Embeddables for pracitcally the same purpose (which I think is going to be a mess of an implementation). See: https://github.com/eclipse-ee4j/jpa-api/issues/105

Anyways, regardless of whether this gets integrated in 5.x, I don't expect it to be integrated in 6.x unless we also reintroduce Composite User Types. I am willing to contribute Composite User Types for 6.x if people see benefit in it and think it can be done in the first place.
This commit is contained in:
Jan-Willem Gmelig Meyling 2020-06-27 20:14:22 +02:00 committed by Sanne Grinovero
parent 69ddb4f782
commit a8a177886e
4 changed files with 74 additions and 10 deletions

View File

@ -36,6 +36,7 @@ import org.hibernate.metamodel.model.domain.internal.MapMember;
import org.hibernate.metamodel.model.domain.internal.MappedSuperclassTypeImpl;
import org.hibernate.metamodel.model.domain.internal.PluralAttributeBuilder;
import org.hibernate.metamodel.model.domain.internal.SingularAttributeImpl;
import org.hibernate.metamodel.model.domain.spi.ManagedTypeDescriptor.InFlightAccess;
import org.hibernate.metamodel.model.domain.spi.PersistentAttributeDescriptor;
import org.hibernate.metamodel.model.domain.spi.EmbeddedTypeDescriptor;
import org.hibernate.metamodel.model.domain.spi.IdentifiableTypeDescriptor;
@ -46,6 +47,7 @@ import org.hibernate.property.access.internal.PropertyAccessMapImpl;
import org.hibernate.property.access.spi.Getter;
import org.hibernate.tuple.entity.EntityMetamodel;
import org.hibernate.type.ComponentType;
import org.hibernate.type.CompositeType;
import org.hibernate.type.EmbeddedComponentType;
import org.hibernate.type.EntityType;
@ -95,11 +97,45 @@ public class AttributeFactory {
return buildPluralAttribute( (PluralAttributeMetadata) attributeMetadata );
}
final SingularAttributeMetadata<X, Y> singularAttributeMetadata = (SingularAttributeMetadata<X, Y>) attributeMetadata;
final SimpleTypeDescriptor<Y> metaModelType = determineSimpleType( singularAttributeMetadata.getValueContext() );
SimpleTypeDescriptor<Y> metaModelType = determineSimpleType( singularAttributeMetadata.getValueContext() );
Attribute.PersistentAttributeType jpaAttributeNature = attributeMetadata.getJpaAttributeNature();
if ( attributeContext.getPropertyMapping().getType().isComponentType() && jpaAttributeNature.equals( Attribute.PersistentAttributeType.BASIC ) ) {
CompositeType compositeType = (CompositeType) attributeContext.getPropertyMapping().getType();
EmbeddableTypeImpl<Y> embeddableType = new EmbeddableTypeImpl<>(
attributeMetadata.getJavaType(),
ownerType,
compositeType,
context.getSessionFactory()
);
context.registerEmbeddedableType(embeddableType);
String[] propertyNames = compositeType.getPropertyNames();
org.hibernate.type.Type[] subtypes = compositeType.getSubtypes();
InFlightAccess<?> inFlightAccess = embeddableType.getInFlightAccess();
for ( int i = 0; i < propertyNames.length; i++ ) {
SingularAttributeImpl nestedAttribute = new SingularAttributeImpl(
embeddableType,
propertyNames[i],
Attribute.PersistentAttributeType.BASIC,
new BasicTypeImpl<Object>(subtypes[i].getReturnedClass(), Type.PersistenceType.BASIC),
null,
false,
false,
property.isOptional()
);
inFlightAccess.addAttribute(nestedAttribute);
}
metaModelType = embeddableType;
jpaAttributeNature = Attribute.PersistentAttributeType.EMBEDDED;
}
return new SingularAttributeImpl(
ownerType,
attributeMetadata.getName(),
attributeMetadata.getJpaAttributeNature(),
jpaAttributeNature,
metaModelType,
attributeMetadata.getMember(),
false,
@ -230,7 +266,7 @@ public class AttributeFactory {
);
context.registerEmbeddedableType( embeddableType );
final ManagedTypeDescriptor.InFlightAccess<Y> inFlightAccess = embeddableType.getInFlightAccess();
final InFlightAccess<Y> inFlightAccess = embeddableType.getInFlightAccess();
final Iterator<Property> subProperties = component.getPropertyIterator();
while ( subProperties.hasNext() ) {
final Property property = subProperties.next();
@ -954,7 +990,7 @@ public class AttributeFactory {
final EmbeddedTypeDescriptor embeddableType = (EmbeddedTypeDescriptor<?>) attributeContext.getOwnerType();
final String attributeName = attributeContext.getPropertyMapping().getName();
final Getter getter = embeddableType.getHibernateType()
final Getter getter = ( ( ComponentType ) embeddableType.getHibernateType() )
.getComponentTuplizer()
.getGetter( embeddableType.getHibernateType().getPropertyIndex( attributeName ) );
return PropertyAccessMapImpl.GetterImpl.class.isInstance( getter )

View File

@ -13,7 +13,7 @@ import org.hibernate.graph.internal.SubGraphImpl;
import org.hibernate.graph.spi.SubGraphImplementor;
import org.hibernate.metamodel.model.domain.spi.EmbeddedTypeDescriptor;
import org.hibernate.metamodel.model.domain.spi.ManagedTypeDescriptor;
import org.hibernate.type.ComponentType;
import org.hibernate.type.CompositeType;
/**
* Standard Hibernate implementation of JPA's {@link javax.persistence.metamodel.EmbeddableType}
@ -27,12 +27,12 @@ public class EmbeddableTypeImpl<J>
implements EmbeddedTypeDescriptor<J>, Serializable {
private final ManagedTypeDescriptor<?> parent;
private final ComponentType hibernateType;
private final CompositeType hibernateType;
public EmbeddableTypeImpl(
Class<J> javaType,
ManagedTypeDescriptor<?> parent,
ComponentType hibernateType,
CompositeType hibernateType,
SessionFactoryImplementor sessionFactory) {
super( javaType, null, null, sessionFactory );
this.parent = parent;
@ -48,7 +48,7 @@ public class EmbeddableTypeImpl<J>
return parent;
}
public ComponentType getHibernateType() {
public CompositeType getHibernateType() {
return hibernateType;
}

View File

@ -9,7 +9,7 @@ package org.hibernate.metamodel.model.domain.spi;
import javax.persistence.metamodel.EmbeddableType;
import org.hibernate.metamodel.model.domain.EmbeddedDomainType;
import org.hibernate.type.ComponentType;
import org.hibernate.type.CompositeType;
/**
* Hibernate extension to the JPA {@link EmbeddableType} descriptor
@ -17,7 +17,7 @@ import org.hibernate.type.ComponentType;
* @author Steve Ebersole
*/
public interface EmbeddedTypeDescriptor<J> extends EmbeddedDomainType<J>, ManagedTypeDescriptor<J> {
ComponentType getHibernateType();
CompositeType getHibernateType();
ManagedTypeDescriptor<?> getParent();
}

View File

@ -6,18 +6,24 @@
*/
package org.hibernate.test.cut;
import java.lang.reflect.Member;
import java.math.BigDecimal;
import java.util.Currency;
import java.util.List;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Configuration;
import org.hibernate.criterion.Restrictions;
import org.hibernate.dialect.DB2Dialect;
import org.hibernate.dialect.HSQLDialect;
import org.hibernate.dialect.SybaseASE15Dialect;
import org.hibernate.hql.internal.ast.QuerySyntaxException;
import org.hibernate.metamodel.model.domain.spi.PersistentAttributeDescriptor;
import org.hibernate.metamodel.model.domain.spi.SingularPersistentAttribute;
import org.hibernate.metamodel.spi.MetamodelImplementor;
import org.hibernate.testing.DialectChecks;
import org.hibernate.testing.RequiresDialectFeature;
import org.hibernate.testing.SkipForDialect;
@ -25,13 +31,20 @@ import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.Test;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.ManagedType;
import javax.persistence.metamodel.SingularAttribute;
import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
/**
* @author Gavin King
*/
public class CompositeUserTypeTest extends BaseCoreFunctionalTestCase {
@Override
public String[] getMappings() {
return new String[] { "cut/types.hbm.xml", "cut/Transaction.hbm.xml" };
@ -67,6 +80,21 @@ public class CompositeUserTypeTest extends BaseCoreFunctionalTestCase {
t.commit();
s.close();
}
@Test
public void testMetamodel() {
MetamodelImplementor metamodel = sessionFactory().getMetamodel();
PersistentAttributeDescriptor<? super Transaction, ?> value = metamodel.managedType(Transaction.class).getAttribute("value");
assertEquals(Attribute.PersistentAttributeType.EMBEDDED, value.getPersistentAttributeType());
SingularPersistentAttribute<?, ?> singularPersistentAttribute = (SingularPersistentAttribute<?, ?>) value;
ManagedType<?> attribute = (ManagedType<?>) singularPersistentAttribute.getType();
SingularAttribute<?, ?> amount = attribute.getSingularAttribute("amount");
assertNotNull(amount);
Member javaMember = amount.getJavaMember();
assertNull(javaMember);
}
@Test
@SkipForDialect ( value = { SybaseASE15Dialect.class }, jiraKey = "HHH-6788" )