diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EmbeddableBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EmbeddableBinder.java index 04064c112e3..d200fd5a45c 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EmbeddableBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EmbeddableBinder.java @@ -42,7 +42,7 @@ import org.hibernate.models.spi.SourceModelBuildingContext; import org.hibernate.models.spi.TypeDetails; import org.hibernate.property.access.internal.PropertyAccessStrategyCompositeUserTypeImpl; -import org.hibernate.property.access.internal.PropertyAccessStrategyMixedImpl; +import org.hibernate.property.access.internal.PropertyAccessStrategyGetterImpl; import org.hibernate.property.access.spi.PropertyAccessStrategy; import org.hibernate.resource.beans.internal.FallbackBeanInstanceProducer; import org.hibernate.resource.beans.spi.ManagedBeanRegistry; @@ -786,7 +786,7 @@ private static void processCompositeUserType(Component component, CompositeUserT for ( Property property : component.getProperties() ) { sortedPropertyNames.add( property.getName() ); sortedPropertyTypes.add( - PropertyAccessStrategyMixedImpl.INSTANCE.buildPropertyAccess( + PropertyAccessStrategyGetterImpl.INSTANCE.buildPropertyAccess( compositeUserType.embeddable(), property.getName(), false diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/ReflectHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/ReflectHelper.java index d51e5cf984d..03791d0488a 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/ReflectHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/ReflectHelper.java @@ -23,7 +23,7 @@ import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.boot.registry.classloading.spi.ClassLoadingException; import org.hibernate.internal.util.collections.ArrayHelper; -import org.hibernate.property.access.internal.PropertyAccessStrategyMixedImpl; +import org.hibernate.property.access.internal.PropertyAccessStrategyGetterImpl; import org.hibernate.property.access.spi.Getter; import jakarta.persistence.Transient; @@ -279,7 +279,7 @@ public static Class reflectedPropertyClass(Class clazz, String name) throw } private static Getter getter(Class clazz, String name) throws MappingException { - return PropertyAccessStrategyMixedImpl.INSTANCE.buildPropertyAccess( clazz, name, true ).getGetter(); + return PropertyAccessStrategyGetterImpl.INSTANCE.buildPropertyAccess( clazz, name, true ).getGetter(); } /** diff --git a/hibernate-core/src/main/java/org/hibernate/property/access/internal/PropertyAccessGetterImpl.java b/hibernate-core/src/main/java/org/hibernate/property/access/internal/PropertyAccessGetterImpl.java new file mode 100644 index 00000000000..275fd02c900 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/property/access/internal/PropertyAccessGetterImpl.java @@ -0,0 +1,89 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.property.access.internal; + +import jakarta.persistence.AccessType; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.hibernate.property.access.spi.Getter; +import org.hibernate.property.access.spi.GetterFieldImpl; +import org.hibernate.property.access.spi.GetterMethodImpl; +import org.hibernate.property.access.spi.PropertyAccess; +import org.hibernate.property.access.spi.PropertyAccessBuildingException; +import org.hibernate.property.access.spi.PropertyAccessStrategy; +import org.hibernate.property.access.spi.Setter; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import static org.hibernate.internal.util.ReflectHelper.getterMethodOrNull; + +/** + * A {@link PropertyAccess} based on mix of getter method or field. + * + * @author Gavin King + */ +public class PropertyAccessGetterImpl implements PropertyAccess { + private final PropertyAccessStrategy strategy; + + private final Getter getter; + + public PropertyAccessGetterImpl(PropertyAccessStrategy strategy, Class containerJavaType, String propertyName) { + this.strategy = strategy; + + final AccessType propertyAccessType = AccessStrategyHelper.getAccessType( containerJavaType, propertyName ); + switch ( propertyAccessType ) { + case FIELD: { + Field field = AccessStrategyHelper.fieldOrNull( containerJavaType, propertyName ); + if ( field == null ) { + throw new PropertyAccessBuildingException( + "Could not locate field for property named [" + containerJavaType.getName() + "#" + propertyName + "]" + ); + } + this.getter = fieldGetter( containerJavaType, propertyName, field ); + break; + } + case PROPERTY: { + Method getterMethod = getterMethodOrNull( containerJavaType, propertyName ); + if ( getterMethod == null ) { + throw new PropertyAccessBuildingException( + "Could not locate getter for property named [" + containerJavaType.getName() + "#" + propertyName + "]" + ); + } + this.getter = propertyGetter( containerJavaType, propertyName, getterMethod ); + break; + } + default: { + throw new PropertyAccessBuildingException( + "Invalid access type " + propertyAccessType + " for property named [" + containerJavaType.getName() + "#" + propertyName + "]" + ); + } + } + } + + // --- // + + private static Getter fieldGetter(Class containerJavaType, String propertyName, Field field) { + return new GetterFieldImpl( containerJavaType, propertyName, field ); + } + + private static Getter propertyGetter(Class containerJavaType, String propertyName, Method method) { + return new GetterMethodImpl( containerJavaType, propertyName, method ); + } + + @Override + public PropertyAccessStrategy getPropertyAccessStrategy() { + return strategy; + } + + @Override + public Getter getGetter() { + return getter; + } + + @Override + public @Nullable Setter getSetter() { + return null; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/property/access/internal/PropertyAccessStrategyGetterImpl.java b/hibernate-core/src/main/java/org/hibernate/property/access/internal/PropertyAccessStrategyGetterImpl.java new file mode 100644 index 00000000000..16f36f92fca --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/property/access/internal/PropertyAccessStrategyGetterImpl.java @@ -0,0 +1,25 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.property.access.internal; + +import org.hibernate.property.access.spi.PropertyAccess; +import org.hibernate.property.access.spi.PropertyAccessStrategy; + +/** + * A PropertyAccessStrategy that selects between available getter method or field. + * + * @author Gavin King + */ +public class PropertyAccessStrategyGetterImpl implements PropertyAccessStrategy { + /** + * Singleton access + */ + public static final PropertyAccessStrategyGetterImpl INSTANCE = new PropertyAccessStrategyGetterImpl(); + + @Override + public PropertyAccess buildPropertyAccess(Class containerJavaType, String propertyName, boolean setterRequired) { + return new PropertyAccessGetterImpl( this, containerJavaType, propertyName ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/strategy/usertype/embedded/record/RecordAsCompositeTypeEmbeddableTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/strategy/usertype/embedded/record/RecordAsCompositeTypeEmbeddableTest.java new file mode 100644 index 00000000000..77dfd04700a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/embeddable/strategy/usertype/embedded/record/RecordAsCompositeTypeEmbeddableTest.java @@ -0,0 +1,169 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.mapping.embeddable.strategy.usertype.embedded.record; + +import jakarta.persistence.AttributeOverride; +import jakarta.persistence.Column; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import org.hibernate.HibernateException; +import org.hibernate.annotations.CompositeType; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.metamodel.spi.ValueAccess; + +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; + +import org.hibernate.usertype.CompositeUserType; + +import org.javamoney.moneta.FastMoney; + +import org.junit.Test; + +import javax.money.MonetaryAmount; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Objects; + +import static org.junit.Assert.assertEquals; + +public class RecordAsCompositeTypeEmbeddableTest extends BaseCoreFunctionalTestCase { + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { RecordAsCompositeTypeEmbeddableEntity.class }; + } + + @Test + public void test() { + MonetaryAmount amount = FastMoney.of( 1, "BRL" ); + + RecordAsCompositeTypeEmbeddableEntity entity = new RecordAsCompositeTypeEmbeddableEntity(); + entity.setAmount( amount ); + entity.setId( 1L ); + + inTransaction( session -> { + session.persist( entity ); + } ); + + inTransaction( session -> { + RecordAsCompositeTypeEmbeddableEntity result = session.find( + RecordAsCompositeTypeEmbeddableEntity.class, + 1L + ); + assertEquals( result.getAmount(), entity.getAmount() ); + } ); + } + + public static class MonetaryAmountType implements CompositeUserType { + public record MonetaryAmountMapper( + BigDecimal amount, + String currency + ) { + } + + ; + + public MonetaryAmountType() { + } + + @Override + public Object getPropertyValue(MonetaryAmount component, int property) throws HibernateException { + //Alphabetical + switch ( property ) { + case 0: + return component.getNumber().numberValueExact( BigDecimal.class ); + case 1: + return component.getCurrency().getCurrencyCode(); + default: + return null; + } + } + + @Override + public MonetaryAmount instantiate(ValueAccess values, SessionFactoryImplementor sessionFactory) { + //Alphabetical + BigDecimal amount = values.getValue( 0, BigDecimal.class ); + String currency = values.getValue( 1, String.class ); + return FastMoney.of( amount, currency ); + } + + @Override + public Class embeddable() { + return MonetaryAmountMapper.class; + } + + @Override + public Class returnedClass() { + return MonetaryAmount.class; + } + + @Override + public boolean equals(MonetaryAmount x, MonetaryAmount y) { + return Objects.equals( x, y ); + } + + @Override + public int hashCode(MonetaryAmount x) { + return x.hashCode(); + } + + @Override + public MonetaryAmount deepCopy(MonetaryAmount value) { + return value; + } + + @Override + public boolean isMutable() { + return false; + } + + @Override + public Serializable disassemble(MonetaryAmount value) { + return (Serializable) value; + } + + @Override + public MonetaryAmount assemble(Serializable cached, Object owner) { + return (MonetaryAmount) cached; + } + + @Override + public MonetaryAmount replace(MonetaryAmount detached, MonetaryAmount managed, Object owner) { + return detached; + } + } + + @Entity(name="RecordAsCompositeTypeEmbeddableEntity") + public static class RecordAsCompositeTypeEmbeddableEntity { + @Id + Long id; + + @Embedded + @AttributeOverride(name = "currency", column = @Column(name = "CURRENCY")) + @AttributeOverride(name = "amount", column = @Column(name = "AMOUNT")) + @CompositeType(MonetaryAmountType.class) + MonetaryAmount amount; + + public RecordAsCompositeTypeEmbeddableEntity() { + } + + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public MonetaryAmount getAmount() { + return amount; + } + + public void setAmount(MonetaryAmount amount) { + this.amount = amount; + } + } +}