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:
parent
69ddb4f782
commit
a8a177886e
|
@ -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 )
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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" };
|
||||
|
@ -68,6 +81,21 @@ public class CompositeUserTypeTest extends BaseCoreFunctionalTestCase {
|
|||
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" )
|
||||
@SkipForDialect ( value = { DB2Dialect.class }, jiraKey = "HHH-6867" )
|
||||
|
|
Loading…
Reference in New Issue