Skip to content

Commit 07927a4

Browse files
committed
Implement support for CompositeUserType and re-enable tests that make use of it
1 parent 277f10d commit 07927a4

File tree

83 files changed

+2081
-464
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

83 files changed

+2081
-464
lines changed

documentation/src/test/java/org/hibernate/userguide/mapping/basic/ColumnTransformerTest.java

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,16 @@
1111
import java.util.Locale;
1212

1313
import org.hibernate.annotations.ColumnTransformer;
14+
import org.hibernate.annotations.CompositeType;
1415
import org.hibernate.dialect.H2Dialect;
1516
import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase;
1617

1718
import org.hibernate.testing.RequiresDialect;
1819
import org.junit.Test;
1920

21+
import jakarta.persistence.AttributeOverride;
22+
import jakarta.persistence.AttributeOverrides;
23+
import jakarta.persistence.Column;
2024
import jakarta.persistence.Entity;
2125
import jakarta.persistence.Id;
2226

@@ -43,15 +47,14 @@ public void testLifecycle() {
4347
//tag::basic-datetime-temporal-date-persist-example[]
4448
Savings savings = new Savings();
4549
savings.setId(1L);
46-
savings.setCurrency(Currency.getInstance(Locale.US));
47-
savings.setAmount(BigDecimal.TEN);
50+
savings.setWallet(new MonetaryAmount(BigDecimal.TEN, Currency.getInstance(Locale.US)));
4851
entityManager.persist(savings);
4952
});
5053

5154
doInJPA(this::entityManagerFactory, entityManager -> {
5255
Savings savings = entityManager.find(Savings.class, 1L);
53-
assertEquals(10, savings.getAmount().intValue());
54-
assertEquals(Currency.getInstance(Locale.US), savings.getCurrency());
56+
assertEquals(10, savings.getWallet().getAmount().intValue());
57+
assertEquals(Currency.getInstance(Locale.US), savings.getWallet().getCurrency());
5558
});
5659
//end::mapping-column-read-and-write-composite-type-persistence-example[]
5760
}
@@ -63,12 +66,17 @@ public static class Savings {
6366
@Id
6467
private Long id;
6568

69+
@CompositeType(MonetaryAmountUserType.class)
70+
@AttributeOverrides({
71+
@AttributeOverride(name = "amount", column = @Column(name = "money")),
72+
@AttributeOverride(name = "currency", column = @Column(name = "currency"))
73+
})
6674
@ColumnTransformer(
67-
read = "amount / 100",
75+
forColumn = "money",
76+
read = "money / 100",
6877
write = "? * 100"
6978
)
70-
private BigDecimal amount;
71-
private Currency currency;
79+
private MonetaryAmount wallet;
7280

7381
//Getters and setters omitted for brevity
7482

@@ -81,20 +89,12 @@ public void setId(Long id) {
8189
this.id = id;
8290
}
8391

84-
public BigDecimal getAmount() {
85-
return amount;
92+
public MonetaryAmount getWallet() {
93+
return wallet;
8694
}
8795

88-
public void setAmount(BigDecimal amount) {
89-
this.amount = amount;
90-
}
91-
92-
public Currency getCurrency() {
93-
return currency;
94-
}
95-
96-
public void setCurrency(Currency currency) {
97-
this.currency = currency;
96+
public void setWallet(MonetaryAmount wallet) {
97+
this.wallet = wallet;
9898
}
9999

100100

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
5+
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
6+
*/
7+
package org.hibernate.userguide.mapping.basic;
8+
9+
import java.io.Serializable;
10+
import java.math.BigDecimal;
11+
import java.util.Currency;
12+
import java.util.function.Supplier;
13+
14+
import org.hibernate.HibernateException;
15+
import org.hibernate.engine.spi.SessionFactoryImplementor;
16+
import org.hibernate.usertype.CompositeUserType;
17+
18+
/**
19+
* @author Emmanuel Bernard
20+
*/
21+
public class MonetaryAmountUserType implements CompositeUserType<MonetaryAmount> {
22+
23+
@Override
24+
public Object getPropertyValue(MonetaryAmount component, int property) throws HibernateException {
25+
switch ( property ) {
26+
case 0:
27+
return component.getAmount();
28+
case 1:
29+
return component.getCurrency();
30+
}
31+
throw new HibernateException( "Illegal property index: " + property );
32+
}
33+
34+
@Override
35+
public MonetaryAmount instantiate(Supplier<Object[]> valueSupplier, SessionFactoryImplementor sessionFactory) {
36+
final Object[] values = valueSupplier.get();
37+
return new MonetaryAmount( (BigDecimal) values[0], (Currency) values[1] );
38+
}
39+
40+
@Override
41+
public Class<?> embeddable() {
42+
return MonetaryAmountEmbeddable.class;
43+
}
44+
45+
@Override
46+
public Class<MonetaryAmount> returnedClass() {
47+
return MonetaryAmount.class;
48+
}
49+
50+
@Override
51+
public boolean isMutable() {
52+
return true;
53+
}
54+
55+
@Override
56+
public Object deepCopy(Object value) {
57+
MonetaryAmount ma = (MonetaryAmount) value;
58+
return new MonetaryAmount( ma.getAmount(), ma.getCurrency() );
59+
}
60+
61+
@Override
62+
public boolean equals(Object x, Object y) {
63+
if ( x == y ) {
64+
return true;
65+
}
66+
if ( x == null || y == null ) {
67+
return false;
68+
}
69+
return x.equals( y );
70+
}
71+
72+
@Override
73+
public Serializable disassemble(Object value) throws HibernateException {
74+
return (Serializable) deepCopy( value );
75+
}
76+
77+
@Override
78+
public Object assemble(Serializable cached, Object owner) throws HibernateException {
79+
return deepCopy( cached );
80+
}
81+
82+
@Override
83+
public Object replace(Object original, Object target, Object owner) throws HibernateException {
84+
return deepCopy( original ); //TODO: improve
85+
}
86+
87+
@Override
88+
public int hashCode(Object x) throws HibernateException {
89+
return x.hashCode();
90+
}
91+
92+
public static class MonetaryAmountEmbeddable {
93+
private BigDecimal amount;
94+
private Currency currency;
95+
}
96+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
5+
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
6+
*/
7+
package org.hibernate.annotations;
8+
9+
import java.lang.annotation.Retention;
10+
11+
import org.hibernate.usertype.CompositeUserType;
12+
13+
import static java.lang.annotation.ElementType.FIELD;
14+
import static java.lang.annotation.ElementType.METHOD;
15+
import static java.lang.annotation.RetentionPolicy.RUNTIME;
16+
17+
/**
18+
* Applies a custom {@link CompositeUserType} for the attribute mapping.
19+
*/
20+
@java.lang.annotation.Target({METHOD, FIELD})
21+
@Retention(RUNTIME)
22+
public @interface CompositeType {
23+
24+
/**
25+
* The custom type implementor class
26+
*/
27+
Class<? extends CompositeUserType<?>> value();
28+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
5+
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
6+
*/
7+
package org.hibernate.annotations;
8+
9+
import java.lang.annotation.Repeatable;
10+
import java.lang.annotation.Retention;
11+
import java.lang.annotation.Target;
12+
13+
import org.hibernate.usertype.CompositeUserType;
14+
15+
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
16+
import static java.lang.annotation.ElementType.PACKAGE;
17+
import static java.lang.annotation.ElementType.TYPE;
18+
import static java.lang.annotation.RetentionPolicy.RUNTIME;
19+
20+
/**
21+
* Registers a custom composite user type implementation to be used
22+
* for all references to a particular {@link jakarta.persistence.Embeddable}.
23+
* <p/>
24+
* May be overridden for a specific embedded using {@link org.hibernate.annotations.CompositeType}
25+
*/
26+
@Target( {TYPE, ANNOTATION_TYPE, PACKAGE} )
27+
@Retention( RUNTIME )
28+
@Repeatable( CompositeTypeRegistrations.class )
29+
public @interface CompositeTypeRegistration {
30+
Class<?> embeddableClass();
31+
Class<? extends CompositeUserType<?>> userType();
32+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
5+
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
6+
*/
7+
package org.hibernate.annotations;
8+
9+
import java.lang.annotation.Retention;
10+
import java.lang.annotation.Target;
11+
12+
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
13+
import static java.lang.annotation.ElementType.PACKAGE;
14+
import static java.lang.annotation.ElementType.TYPE;
15+
import static java.lang.annotation.RetentionPolicy.RUNTIME;
16+
17+
/**
18+
* Grouping of {@link CompositeTypeRegistration}
19+
*
20+
* @author Steve Ebersole
21+
*/
22+
@Target( {TYPE, ANNOTATION_TYPE, PACKAGE} )
23+
@Retention( RUNTIME )
24+
public @interface CompositeTypeRegistrations {
25+
CompositeTypeRegistration[] value();
26+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
5+
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
6+
*/
7+
package org.hibernate.annotations;
8+
9+
import org.hibernate.usertype.CompositeUserType;
10+
11+
/**
12+
* Form of {@link CompositeType} for use with map-keys
13+
*
14+
* @since 6.0
15+
*/
16+
public @interface MapKeyCustomCompositeType {
17+
/**
18+
* The custom type implementor class
19+
*
20+
* @see CompositeType#value
21+
*/
22+
Class<? extends CompositeUserType<?>> value();
23+
}

hibernate-core/src/main/java/org/hibernate/boot/internal/InFlightMetadataCollectorImpl.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@
104104
import org.hibernate.type.descriptor.java.JavaType;
105105
import org.hibernate.type.descriptor.jdbc.JdbcType;
106106
import org.hibernate.type.spi.TypeConfiguration;
107+
import org.hibernate.usertype.CompositeUserType;
107108

108109
import jakarta.persistence.AttributeConverter;
109110
import jakarta.persistence.Embeddable;
@@ -410,6 +411,25 @@ public Class<? extends EmbeddableInstantiator> findRegisteredEmbeddableInstantia
410411
return registeredInstantiators.get( embeddableType );
411412
}
412413

414+
private Map<Class<?>, Class<? extends CompositeUserType<?>>> registeredCompositeUserTypes;
415+
416+
@Override
417+
public void registerCompositeUserType(Class<?> embeddableType, Class<? extends CompositeUserType<?>> userType) {
418+
if ( registeredCompositeUserTypes == null ) {
419+
registeredCompositeUserTypes = new HashMap<>();
420+
}
421+
registeredCompositeUserTypes.put( embeddableType, userType );
422+
}
423+
424+
@Override
425+
public Class<? extends CompositeUserType<?>> findRegisteredCompositeUserType(Class<?> embeddableType) {
426+
if ( registeredCompositeUserTypes == null ) {
427+
return null;
428+
}
429+
430+
return registeredCompositeUserTypes.get( embeddableType );
431+
}
432+
413433
private Map<CollectionClassification, CollectionTypeRegistrationDescriptor> collectionTypeRegistrations;
414434

415435

hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/ModelBinder.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@
153153
import org.hibernate.type.CustomType;
154154
import org.hibernate.type.ForeignKeyDirection;
155155
import org.hibernate.type.StandardBasicTypes;
156+
import org.hibernate.usertype.CompositeUserType;
156157
import org.hibernate.usertype.ParameterizedType;
157158
import org.hibernate.usertype.UserType;
158159

@@ -2636,6 +2637,22 @@ else if ( isVirtual ) {
26362637
else {
26372638
log.debugf( "Binding component [%s]", role );
26382639
if ( StringHelper.isNotEmpty( explicitComponentClassName ) ) {
2640+
try {
2641+
final Class<Object> componentClass = sourceDocument.getBootstrapContext().getClassLoaderAccess()
2642+
.classForName( explicitComponentClassName );
2643+
if ( CompositeUserType.class.isAssignableFrom( componentClass ) ) {
2644+
componentBinding.setTypeName( explicitComponentClassName );
2645+
CompositeUserType<?> compositeUserType = (CompositeUserType<?>) sourceDocument.getBootstrapContext()
2646+
.getServiceRegistry()
2647+
.getService( ManagedBeanRegistry.class )
2648+
.getBean( componentClass )
2649+
.getBeanInstance();
2650+
explicitComponentClassName = compositeUserType.embeddable().getName();
2651+
}
2652+
}
2653+
catch (ClassLoadingException ex) {
2654+
log.debugf( ex, "Could load component class [%s]", explicitComponentClassName );
2655+
}
26392656
log.debugf( "Binding component [%s] to explicitly specified class", role, explicitComponentClassName );
26402657
componentBinding.setComponentClassName( explicitComponentClassName );
26412658
}

hibernate-core/src/main/java/org/hibernate/boot/query/HbmResultSetMappingDescriptor.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -653,11 +653,11 @@ public FetchMemento resolve(ResultSetMappingResolutionContext resolutionContext)
653653

654654
final FetchParentMemento fetchParentMemento = parent.resolveParentMemento( resolutionContext );
655655

656-
NavigablePath navigablePath = fetchParentMemento.getNavigablePath().append( propertyPathParts[ 0 ] );
657656
Fetchable fetchable = (Fetchable) fetchParentMemento.getFetchableContainer().findSubPart(
658657
propertyPathParts[ 0 ],
659658
null
660659
);
660+
NavigablePath navigablePath = fetchParentMemento.getNavigablePath().append( fetchable.getFetchableName() );
661661

662662
for ( int i = 1; i < propertyPathParts.length; i++ ) {
663663
if ( ! ( fetchable instanceof FetchableContainer ) ) {
@@ -666,8 +666,8 @@ public FetchMemento resolve(ResultSetMappingResolutionContext resolutionContext)
666666
+ " did not reference FetchableContainer"
667667
);
668668
}
669-
navigablePath = navigablePath.append( propertyPathParts[ i ] );
670669
fetchable = (Fetchable) ( (FetchableContainer) fetchable ).findSubPart( propertyPathParts[i], null );
670+
navigablePath = navigablePath.append( fetchable.getFetchableName() );
671671
}
672672

673673
if ( fetchable instanceof BasicValuedModelPart ) {

0 commit comments

Comments
 (0)