Skip to content

Commit 6f91c1c

Browse files
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: jakartaee/persistence#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.
1 parent f270f68 commit 6f91c1c

File tree

4 files changed

+74
-10
lines changed

4 files changed

+74
-10
lines changed

Diff for: hibernate-core/src/main/java/org/hibernate/metamodel/internal/AttributeFactory.java

+40-4
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.hibernate.metamodel.model.domain.internal.MappedSuperclassTypeImpl;
3737
import org.hibernate.metamodel.model.domain.internal.PluralAttributeBuilder;
3838
import org.hibernate.metamodel.model.domain.internal.SingularAttributeImpl;
39+
import org.hibernate.metamodel.model.domain.spi.ManagedTypeDescriptor.InFlightAccess;
3940
import org.hibernate.metamodel.model.domain.spi.PersistentAttributeDescriptor;
4041
import org.hibernate.metamodel.model.domain.spi.EmbeddedTypeDescriptor;
4142
import org.hibernate.metamodel.model.domain.spi.IdentifiableTypeDescriptor;
@@ -46,6 +47,7 @@
4647
import org.hibernate.property.access.spi.Getter;
4748
import org.hibernate.tuple.entity.EntityMetamodel;
4849
import org.hibernate.type.ComponentType;
50+
import org.hibernate.type.CompositeType;
4951
import org.hibernate.type.EmbeddedComponentType;
5052
import org.hibernate.type.EntityType;
5153

@@ -95,11 +97,45 @@ public <X, Y> PersistentAttributeDescriptor<X, Y> buildAttribute(ManagedTypeDesc
9597
return buildPluralAttribute( (PluralAttributeMetadata) attributeMetadata );
9698
}
9799
final SingularAttributeMetadata<X, Y> singularAttributeMetadata = (SingularAttributeMetadata<X, Y>) attributeMetadata;
98-
final SimpleTypeDescriptor<Y> metaModelType = determineSimpleType( singularAttributeMetadata.getValueContext() );
100+
SimpleTypeDescriptor<Y> metaModelType = determineSimpleType( singularAttributeMetadata.getValueContext() );
101+
Attribute.PersistentAttributeType jpaAttributeNature = attributeMetadata.getJpaAttributeNature();
102+
103+
if ( attributeContext.getPropertyMapping().getType().isComponentType() && jpaAttributeNature.equals( Attribute.PersistentAttributeType.BASIC ) ) {
104+
CompositeType compositeType = (CompositeType) attributeContext.getPropertyMapping().getType();
105+
EmbeddableTypeImpl<Y> embeddableType = new EmbeddableTypeImpl<>(
106+
attributeMetadata.getJavaType(),
107+
ownerType,
108+
compositeType,
109+
context.getSessionFactory()
110+
);
111+
context.registerEmbeddedableType(embeddableType);
112+
113+
String[] propertyNames = compositeType.getPropertyNames();
114+
org.hibernate.type.Type[] subtypes = compositeType.getSubtypes();
115+
InFlightAccess<?> inFlightAccess = embeddableType.getInFlightAccess();
116+
117+
for ( int i = 0; i < propertyNames.length; i++ ) {
118+
SingularAttributeImpl nestedAttribute = new SingularAttributeImpl(
119+
embeddableType,
120+
propertyNames[i],
121+
Attribute.PersistentAttributeType.BASIC,
122+
new BasicTypeImpl<Object>(subtypes[i].getReturnedClass(), Type.PersistenceType.BASIC),
123+
null,
124+
false,
125+
false,
126+
property.isOptional()
127+
);
128+
inFlightAccess.addAttribute(nestedAttribute);
129+
}
130+
131+
metaModelType = embeddableType;
132+
jpaAttributeNature = Attribute.PersistentAttributeType.EMBEDDED;
133+
}
134+
99135
return new SingularAttributeImpl(
100136
ownerType,
101137
attributeMetadata.getName(),
102-
attributeMetadata.getJpaAttributeNature(),
138+
jpaAttributeNature,
103139
metaModelType,
104140
attributeMetadata.getMember(),
105141
false,
@@ -230,7 +266,7 @@ private <Y> SimpleTypeDescriptor<Y> determineSimpleType(ValueContext typeContext
230266
);
231267
context.registerEmbeddedableType( embeddableType );
232268

233-
final ManagedTypeDescriptor.InFlightAccess<Y> inFlightAccess = embeddableType.getInFlightAccess();
269+
final InFlightAccess<Y> inFlightAccess = embeddableType.getInFlightAccess();
234270
final Iterator<Property> subProperties = component.getPropertyIterator();
235271
while ( subProperties.hasNext() ) {
236272
final Property property = subProperties.next();
@@ -954,7 +990,7 @@ public Member resolveMember(AttributeContext attributeContext) {
954990
final EmbeddedTypeDescriptor embeddableType = (EmbeddedTypeDescriptor<?>) attributeContext.getOwnerType();
955991
final String attributeName = attributeContext.getPropertyMapping().getName();
956992

957-
final Getter getter = embeddableType.getHibernateType()
993+
final Getter getter = ( ( ComponentType ) embeddableType.getHibernateType() )
958994
.getComponentTuplizer()
959995
.getGetter( embeddableType.getHibernateType().getPropertyIndex( attributeName ) );
960996
return PropertyAccessMapImpl.GetterImpl.class.isInstance( getter )

Diff for: hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/EmbeddableTypeImpl.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import org.hibernate.graph.spi.SubGraphImplementor;
1414
import org.hibernate.metamodel.model.domain.spi.EmbeddedTypeDescriptor;
1515
import org.hibernate.metamodel.model.domain.spi.ManagedTypeDescriptor;
16-
import org.hibernate.type.ComponentType;
16+
import org.hibernate.type.CompositeType;
1717

1818
/**
1919
* Standard Hibernate implementation of JPA's {@link javax.persistence.metamodel.EmbeddableType}
@@ -27,12 +27,12 @@ public class EmbeddableTypeImpl<J>
2727
implements EmbeddedTypeDescriptor<J>, Serializable {
2828

2929
private final ManagedTypeDescriptor<?> parent;
30-
private final ComponentType hibernateType;
30+
private final CompositeType hibernateType;
3131

3232
public EmbeddableTypeImpl(
3333
Class<J> javaType,
3434
ManagedTypeDescriptor<?> parent,
35-
ComponentType hibernateType,
35+
CompositeType hibernateType,
3636
SessionFactoryImplementor sessionFactory) {
3737
super( javaType, null, null, sessionFactory );
3838
this.parent = parent;
@@ -48,7 +48,7 @@ public ManagedTypeDescriptor<?> getParent() {
4848
return parent;
4949
}
5050

51-
public ComponentType getHibernateType() {
51+
public CompositeType getHibernateType() {
5252
return hibernateType;
5353
}
5454

Diff for: hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/spi/EmbeddedTypeDescriptor.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@
99
import javax.persistence.metamodel.EmbeddableType;
1010

1111
import org.hibernate.metamodel.model.domain.EmbeddedDomainType;
12-
import org.hibernate.type.ComponentType;
12+
import org.hibernate.type.CompositeType;
1313

1414
/**
1515
* Hibernate extension to the JPA {@link EmbeddableType} descriptor
1616
*
1717
* @author Steve Ebersole
1818
*/
1919
public interface EmbeddedTypeDescriptor<J> extends EmbeddedDomainType<J>, ManagedTypeDescriptor<J> {
20-
ComponentType getHibernateType();
20+
CompositeType getHibernateType();
2121

2222
ManagedTypeDescriptor<?> getParent();
2323
}

Diff for: hibernate-core/src/test/java/org/hibernate/test/cut/CompositeUserTypeTest.java

+28
Original file line numberDiff line numberDiff line change
@@ -6,32 +6,45 @@
66
*/
77
package org.hibernate.test.cut;
88

9+
import java.lang.reflect.Member;
910
import java.math.BigDecimal;
1011
import java.util.Currency;
1112
import java.util.List;
1213

1314
import org.hibernate.Query;
1415
import org.hibernate.Session;
16+
import org.hibernate.cfg.AvailableSettings;
17+
import org.hibernate.cfg.Configuration;
1518
import org.hibernate.criterion.Restrictions;
1619
import org.hibernate.dialect.DB2Dialect;
1720
import org.hibernate.dialect.HSQLDialect;
1821
import org.hibernate.dialect.SybaseASE15Dialect;
1922
import org.hibernate.hql.internal.ast.QuerySyntaxException;
2023

24+
import org.hibernate.metamodel.model.domain.spi.PersistentAttributeDescriptor;
25+
import org.hibernate.metamodel.model.domain.spi.SingularPersistentAttribute;
26+
import org.hibernate.metamodel.spi.MetamodelImplementor;
2127
import org.hibernate.testing.DialectChecks;
2228
import org.hibernate.testing.RequiresDialectFeature;
2329
import org.hibernate.testing.SkipForDialect;
2430
import org.hibernate.testing.TestForIssue;
2531
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
2632
import org.junit.Test;
2733

34+
import javax.persistence.metamodel.Attribute;
35+
import javax.persistence.metamodel.ManagedType;
36+
import javax.persistence.metamodel.SingularAttribute;
37+
2838
import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping;
2939
import static org.junit.Assert.assertEquals;
40+
import static org.junit.Assert.assertNotNull;
41+
import static org.junit.Assert.assertNull;
3042

3143
/**
3244
* @author Gavin King
3345
*/
3446
public class CompositeUserTypeTest extends BaseCoreFunctionalTestCase {
47+
3548
@Override
3649
public String[] getMappings() {
3750
return new String[] { "cut/types.hbm.xml", "cut/Transaction.hbm.xml" };
@@ -67,6 +80,21 @@ public void testCompositeUserType() {
6780
t.commit();
6881
s.close();
6982
}
83+
84+
@Test
85+
public void testMetamodel() {
86+
MetamodelImplementor metamodel = sessionFactory().getMetamodel();
87+
PersistentAttributeDescriptor<? super Transaction, ?> value = metamodel.managedType(Transaction.class).getAttribute("value");
88+
assertEquals(Attribute.PersistentAttributeType.EMBEDDED, value.getPersistentAttributeType());
89+
90+
SingularPersistentAttribute<?, ?> singularPersistentAttribute = (SingularPersistentAttribute<?, ?>) value;
91+
ManagedType<?> attribute = (ManagedType<?>) singularPersistentAttribute.getType();
92+
SingularAttribute<?, ?> amount = attribute.getSingularAttribute("amount");
93+
assertNotNull(amount);
94+
95+
Member javaMember = amount.getJavaMember();
96+
assertNull(javaMember);
97+
}
7098

7199
@Test
72100
@SkipForDialect ( value = { SybaseASE15Dialect.class }, jiraKey = "HHH-6788" )

0 commit comments

Comments
 (0)