HHH-15898 Add @Instantiator annotation to mark canonical constructor for embeddables

This commit is contained in:
Christian Beikov 2022-12-21 11:55:38 +01:00
parent e19727e454
commit ba985518c7
11 changed files with 577 additions and 44 deletions

View File

@ -0,0 +1,31 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.annotations;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import org.hibernate.Incubating;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Marks the canonical constructor to be used for instantation of an embeddable.
* This will implicitly add a special {@link EmbeddableInstantiator}.
*
* @since 6.2
*/
@Target({ CONSTRUCTOR })
@Retention(RUNTIME)
@Incubating
public @interface Instantiator {
/**
* The persistent attribute names the constructor parameters at the respective index assigns the value to.
*/
String[] value();
}

View File

@ -7,7 +7,6 @@
package org.hibernate.cfg;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
@ -33,6 +32,7 @@ import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.UserDefinedType;
import org.hibernate.mapping.Value;
import org.hibernate.metamodel.internal.EmbeddableHelper;
import org.hibernate.sql.Template;
import org.hibernate.type.BasicPluralType;
import org.hibernate.type.BasicType;
@ -195,12 +195,20 @@ public class AggregateComponentSecondPass implements SecondPass {
}
else {
final String[] componentNames = ReflectHelper.getRecordComponentNames( componentClass );
propertyMappingIndex = determinePropertyMappingIndex( componentNames );
propertyMappingIndex = EmbeddableHelper.determinePropertyMappingIndex(
component.getPropertyNames(),
componentNames
);
}
}
else {
// At some point we could do some byte code analysis to determine the order based on a constructor
return;
if ( component.getInstantiatorPropertyNames() == null ) {
return;
}
propertyMappingIndex = EmbeddableHelper.determinePropertyMappingIndex(
component.getPropertyNames(),
component.getInstantiatorPropertyNames()
);
}
final ArrayList<Column> orderedColumns = new ArrayList<>( userDefinedType.getColumnSpan() );
final List<Property> properties = component.getProperties();
@ -210,8 +218,7 @@ public class AggregateComponentSecondPass implements SecondPass {
}
}
else {
for ( int newIndex = 0; newIndex < propertyMappingIndex.length; newIndex++ ) {
final int propertyIndex = propertyMappingIndex[newIndex];
for ( final int propertyIndex : propertyMappingIndex ) {
addColumns( orderedColumns, properties.get( propertyIndex ).getValue() );
}
}
@ -235,17 +242,6 @@ public class AggregateComponentSecondPass implements SecondPass {
}
}
private static int[] determinePropertyMappingIndex(String[] componentNames) {
final String[] sortedComponentNames = componentNames.clone();
final int[] index = new int[componentNames.length];
Arrays.sort( sortedComponentNames );
for ( int i = 0; i < componentNames.length; i++ ) {
final int newIndex = Arrays.binarySearch( sortedComponentNames, componentNames[i] );
index[newIndex] = i;
}
return index;
}
private void validateSupportedColumnTypes(String basePath, Component component) {
for ( Property property : component.getProperties() ) {
final Value value = property.getValue();

View File

@ -7,6 +7,7 @@
package org.hibernate.cfg;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.time.OffsetDateTime;
@ -42,6 +43,7 @@ import org.hibernate.annotations.GenericGenerators;
import org.hibernate.annotations.IdGeneratorType;
import org.hibernate.annotations.Imported;
import org.hibernate.annotations.Index;
import org.hibernate.annotations.Instantiator;
import org.hibernate.annotations.JavaTypeRegistration;
import org.hibernate.annotations.JavaTypeRegistrations;
import org.hibernate.annotations.JdbcTypeRegistration;
@ -2330,6 +2332,10 @@ public final class AnnotationBinder {
component.setComponentClassName( inferredData.getClassOrElementName() );
}
component.setCustomInstantiator( customInstantiatorImpl );
final Constructor<?> constructor = resolveInstantiator( inferredData.getClassOrElement(), context );
if ( constructor != null ) {
component.setInstantiator( constructor, constructor.getAnnotation( Instantiator.class ).value() );
}
return component;
}
@ -2578,4 +2584,24 @@ public final class AnnotationBinder {
}
}
}
public static Constructor<?> resolveInstantiator(XClass embeddableClass, MetadataBuildingContext buildingContext) {
if ( embeddableClass != null ) {
final Constructor<?>[] declaredConstructors = buildingContext.getBootstrapContext().getReflectionManager()
.toClass( embeddableClass )
.getDeclaredConstructors();
Constructor<?> constructor = null;
for ( Constructor<?> declaredConstructor : declaredConstructors ) {
if ( declaredConstructor.isAnnotationPresent( Instantiator.class ) ) {
if ( constructor != null ) {
throw new MappingException(
"Multiple constructors annotated with @Instantiator but only one constructor can be the canonical constructor in class: " + embeddableClass.getName() );
}
constructor = declaredConstructor;
}
}
return constructor;
}
return null;
}
}

View File

@ -6,6 +6,7 @@
*/
package org.hibernate.mapping;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
@ -17,6 +18,7 @@ import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import org.hibernate.Internal;
import org.hibernate.MappingException;
import org.hibernate.Remove;
import org.hibernate.boot.model.relational.Database;
@ -27,12 +29,14 @@ import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.boot.registry.classloading.spi.ClassLoadingException;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.Mapping;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.id.CompositeNestedGeneratedValueGenerator;
import org.hibernate.id.IdentifierGenerator;
import org.hibernate.id.factory.IdentifierGeneratorFactory;
import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.internal.util.collections.JoinedIterator;
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
import org.hibernate.property.access.spi.Setter;
@ -69,6 +73,8 @@ public class Component extends SimpleValue implements MetaAttributable, Sortable
private Map<String,MetaAttribute> metaAttributes;
private Class<? extends EmbeddableInstantiator> customInstantiator;
private Constructor<?> instantiator;
private String[] instantiatorPropertyNames;
// cache the status of the type
private volatile Type type;
@ -614,6 +620,15 @@ public class Component extends SimpleValue implements MetaAttributable, Sortable
}
}
@Internal
public String[] getPropertyNames() {
final String[] propertyNames = new String[properties.size()];
for ( int i = 0; i < properties.size(); i++ ) {
propertyNames[i] = properties.get( i ).getName();
}
return propertyNames;
}
public static class StandardGenerationContextLocator
implements CompositeNestedGeneratedValueGenerator.GenerationContextLocator {
private final String entityName;
@ -668,6 +683,35 @@ public class Component extends SimpleValue implements MetaAttributable, Sortable
getType();
}
@Override
public boolean isValid(Mapping mapping) throws MappingException {
if ( !super.isValid( mapping ) ) {
return false;
}
if ( instantiatorPropertyNames != null ) {
if ( instantiatorPropertyNames.length < properties.size() ) {
throw new MappingException( "component type [" + componentClassName + "] specifies " + instantiatorPropertyNames.length + " properties for the instantiator but has " + properties.size() + " properties" );
}
final HashSet<String> assignedPropertyNames = CollectionHelper.setOfSize( properties.size() );
for ( String instantiatorPropertyName : instantiatorPropertyNames ) {
if ( getProperty( instantiatorPropertyName ) == null ) {
throw new MappingException( "could not find property [" + instantiatorPropertyName + "] defined in the @Instantiator withing component [" + componentClassName + "]" );
}
assignedPropertyNames.add( instantiatorPropertyName );
}
if ( assignedPropertyNames.size() != properties.size() ) {
final ArrayList<String> missingProperties = new ArrayList<>();
for ( Property property : properties ) {
if ( !assignedPropertyNames.contains( property.getName() ) ) {
missingProperties.add( property.getName() );
}
}
throw new MappingException( "component type [" + componentClassName + "] has " + properties.size() + " properties but the instantiator only assigns " + assignedPropertyNames.size() + " properties. missing properties: " + missingProperties );
}
}
return true;
}
@Override
public boolean isSorted() {
return originalPropertyOrder != ArrayHelper.EMPTY_INT_ARRAY;
@ -678,7 +722,6 @@ public class Component extends SimpleValue implements MetaAttributable, Sortable
return sortProperties( false );
}
private int[] sortProperties(boolean forceRetainOriginalOrder) {
if ( originalPropertyOrder != ArrayHelper.EMPTY_INT_ARRAY ) {
return originalPropertyOrder;
@ -750,4 +793,18 @@ public class Component extends SimpleValue implements MetaAttributable, Sortable
public void setCustomInstantiator(Class<? extends EmbeddableInstantiator> customInstantiator) {
this.customInstantiator = customInstantiator;
}
public Constructor<?> getInstantiator() {
return instantiator;
}
public String[] getInstantiatorPropertyNames() {
return instantiatorPropertyNames;
}
public void setInstantiator(Constructor<?> instantiator, String[] instantiatorPropertyNames) {
this.instantiator = instantiator;
this.instantiatorPropertyNames = instantiatorPropertyNames;
}
}

View File

@ -0,0 +1,84 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.metamodel.internal;
import java.lang.reflect.Constructor;
import org.hibernate.InstantiationException;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.spi.ValueAccess;
/**
* Support for instantiating embeddables as POJO representation through a constructor
*/
public class EmbeddableInstantiatorPojoIndirecting extends AbstractPojoInstantiator implements StandardEmbeddableInstantiator {
protected final Constructor<?> constructor;
protected final int[] index;
protected EmbeddableInstantiatorPojoIndirecting(Constructor<?> constructor, int[] index) {
super( constructor.getDeclaringClass() );
this.constructor = constructor;
this.index = index;
}
public static EmbeddableInstantiatorPojoIndirecting of(
String[] propertyNames,
Constructor<?> constructor,
String[] componentNames) {
if ( componentNames == null ) {
throw new IllegalArgumentException( "Can't determine field assignment for constructor: " + constructor );
}
final int[] index = new int[componentNames.length];
if ( EmbeddableHelper.resolveIndex( propertyNames, componentNames, index ) ) {
return new EmbeddableInstantiatorPojoIndirecting.EmbeddableInstantiatorPojoIndirectingWithGap( constructor, index );
}
else {
return new EmbeddableInstantiatorPojoIndirecting( constructor, index );
}
}
@Override
public Object instantiate(ValueAccess valuesAccess, SessionFactoryImplementor sessionFactory) {
try {
final Object[] originalValues = valuesAccess.getValues();
final Object[] values = new Object[originalValues.length];
for ( int i = 0; i < values.length; i++ ) {
values[i] = originalValues[index[i]];
}
return constructor.newInstance( values );
}
catch ( Exception e ) {
throw new InstantiationException( "Could not instantiate entity: ", getMappedPojoClass(), e );
}
}
// Handles gaps, by leaving the value null for that index
private static class EmbeddableInstantiatorPojoIndirectingWithGap extends EmbeddableInstantiatorPojoIndirecting {
public EmbeddableInstantiatorPojoIndirectingWithGap(Constructor<?> constructor, int[] index) {
super( constructor, index );
}
@Override
public Object instantiate(ValueAccess valuesAccess, SessionFactoryImplementor sessionFactory) {
try {
final Object[] originalValues = valuesAccess.getValues();
final Object[] values = new Object[index.length];
for ( int i = 0; i < values.length; i++ ) {
final int index = this.index[i];
if ( index >= 0 ) {
values[i] = originalValues[index];
}
}
return constructor.newInstance( values );
}
catch ( Exception e ) {
throw new InstantiationException( "Could not instantiate entity: ", getMappedPojoClass(), e );
}
}
}
}

View File

@ -26,7 +26,6 @@ public class EmbeddableInstantiatorPojoStandard extends AbstractPojoInstantiator
private static final CoreMessageLogger LOG = CoreLogging.messageLogger( PojoInstantiatorImpl.class );
private final Supplier<EmbeddableMappingType> embeddableMappingAccess;
private final boolean constructorInjection = false;
private final Constructor<?> constructor;
public EmbeddableInstantiatorPojoStandard(JavaType<?> javaType, Supplier<EmbeddableMappingType> embeddableMappingAccess) {
@ -38,7 +37,7 @@ public class EmbeddableInstantiatorPojoStandard extends AbstractPojoInstantiator
protected static Constructor<?> resolveConstructor(Class<?> mappedPojoClass) {
try {
return ReflectHelper.getDefaultConstructor( mappedPojoClass);
return ReflectHelper.getDefaultConstructor( mappedPojoClass );
}
catch ( PropertyNotFoundException e ) {
LOG.noDefaultConstructor( mappedPojoClass.getName() );
@ -60,11 +59,7 @@ public class EmbeddableInstantiatorPojoStandard extends AbstractPojoInstantiator
}
try {
if ( constructorInjection ) {
return constructor.newInstance( valuesAccess.getValues() );
}
Object[] values = valuesAccess == null ? null : valuesAccess.getValues();
final Object[] values = valuesAccess == null ? null : valuesAccess.getValues();
final Object instance = constructor.newInstance();
if ( values != null ) {
// At this point, createEmptyCompositesEnabled is always true.

View File

@ -6,8 +6,6 @@
*/
package org.hibernate.metamodel.internal;
import java.util.Arrays;
import org.hibernate.InstantiationException;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.ReflectHelper;
@ -25,10 +23,10 @@ public class EmbeddableInstantiatorRecordIndirecting extends EmbeddableInstantia
this.index = index;
}
public static EmbeddableInstantiatorRecordIndirecting of(Class<?> javaType) {
public static EmbeddableInstantiatorRecordIndirecting of(Class<?> javaType, String[] propertyNames) {
final String[] componentNames = ReflectHelper.getRecordComponentNames( javaType );
final int[] index = new int[componentNames.length];
if ( resolveIndex( componentNames, index ) ) {
if ( EmbeddableHelper.resolveIndex( propertyNames, componentNames, index ) ) {
return new EmbeddableInstantiatorRecordIndirectingWithGap( javaType, index );
}
else {
@ -36,19 +34,6 @@ public class EmbeddableInstantiatorRecordIndirecting extends EmbeddableInstantia
}
}
private static boolean resolveIndex(String[] componentNames, int[] index) {
final String[] sortedComponentNames = componentNames.clone();
Arrays.sort( sortedComponentNames );
boolean hasGaps = false;
for ( int i = 0; i < componentNames.length; i++ ) {
final int newIndex = Arrays.binarySearch( sortedComponentNames, componentNames[i] );
index[i] = newIndex;
hasGaps = hasGaps || newIndex < 0;
}
return hasGaps;
}
@Override
public Object instantiate(ValueAccess valuesAccess, SessionFactoryImplementor sessionFactory) {
if ( constructor == null ) {

View File

@ -186,7 +186,7 @@ public class EmbeddableRepresentationStrategyPojo extends AbstractEmbeddableRepr
return null;
}
if ( hasCustomAccessors() || bootDescriptor.getCustomInstantiator() != null ) {
if ( hasCustomAccessors() || bootDescriptor.getCustomInstantiator() != null || bootDescriptor.getInstantiator() != null ) {
return null;
}

View File

@ -110,9 +110,20 @@ public class ManagedTypeRepresentationResolverStandard implements ManagedTypeRep
customInstantiator = new EmbeddableInstantiatorRecordStandard( bootDescriptor.getComponentClass() );
}
else {
customInstantiator = EmbeddableInstantiatorRecordIndirecting.of( bootDescriptor.getComponentClass() );
customInstantiator = EmbeddableInstantiatorRecordIndirecting.of(
bootDescriptor.getComponentClass(),
bootDescriptor.getPropertyNames()
);
}
}
else if ( bootDescriptor.getInstantiator() != null ) {
bootDescriptor.sortProperties();
customInstantiator = EmbeddableInstantiatorPojoIndirecting.of(
bootDescriptor.getPropertyNames(),
bootDescriptor.getInstantiator(),
bootDescriptor.getInstantiatorPropertyNames()
);
}
else {
customInstantiator = null;
}

View File

@ -0,0 +1,340 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.orm.test.component;
import java.util.List;
import java.util.Objects;
import org.hibernate.annotations.Instantiator;
import org.hibernate.annotations.Struct;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.dialect.OracleDialect;
import org.hibernate.dialect.PostgreSQLDialect;
import org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl;
import org.hibernate.testing.orm.junit.BaseSessionFactoryFunctionalTest;
import org.hibernate.testing.orm.junit.RequiresDialect;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import jakarta.persistence.Embeddable;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Tuple;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNull;
@RequiresDialect( PostgreSQLDialect.class )
@RequiresDialect( OracleDialect.class )
public class StructComponentInstantiatorTest extends BaseSessionFactoryFunctionalTest {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
RecordStructHolder.class
};
}
@Override
public StandardServiceRegistry produceServiceRegistry(StandardServiceRegistryBuilder ssrBuilder) {
// Make sure this stuff runs on a dedicated connection pool,
// otherwise we might run into ORA-21700: object does not exist or is marked for delete
// because the JDBC connection or database session caches something that should have been invalidated
ssrBuilder.applySetting( AvailableSettings.CONNECTION_PROVIDER, DriverManagerConnectionProviderImpl.class.getName() );
return super.produceServiceRegistry( ssrBuilder );
}
@BeforeEach
public void setUp() {
inTransaction(
session -> {
session.persist( new RecordStructHolder( 1L, Point.createAggregate1() ) );
session.persist( new RecordStructHolder( 2L, Point.createAggregate2() ) );
}
);
}
@AfterEach
protected void cleanupTest() {
inTransaction(
session -> {
session.createQuery( "delete from RecordStructHolder h" ).executeUpdate();
}
);
}
@Test
public void testUpdate() {
sessionFactoryScope().inTransaction(
entityManager -> {
RecordStructHolder RecordStructHolder = entityManager.find( RecordStructHolder.class, 1L );
RecordStructHolder.setThePoint( Point.createAggregate2() );
entityManager.flush();
entityManager.clear();
assertStructEquals( Point.createAggregate2(), entityManager.find( RecordStructHolder.class, 1L ).getThePoint() );
}
);
}
@Test
public void testFetch() {
sessionFactoryScope().inSession(
entityManager -> {
List<RecordStructHolder> RecordStructHolders = entityManager.createQuery( "from RecordStructHolder b where b.id = 1", RecordStructHolder.class ).getResultList();
assertEquals( 1, RecordStructHolders.size() );
assertEquals( 1L, RecordStructHolders.get( 0 ).getId() );
assertStructEquals( Point.createAggregate1(), RecordStructHolders.get( 0 ).getThePoint() );
}
);
}
@Test
public void testFetchNull() {
sessionFactoryScope().inSession(
entityManager -> {
List<RecordStructHolder> RecordStructHolders = entityManager.createQuery( "from RecordStructHolder b where b.id = 2", RecordStructHolder.class ).getResultList();
assertEquals( 1, RecordStructHolders.size() );
assertEquals( 2L, RecordStructHolders.get( 0 ).getId() );
assertStructEquals( Point.createAggregate2(), RecordStructHolders.get( 0 ).getThePoint() );
}
);
}
@Test
public void testDomainResult() {
sessionFactoryScope().inSession(
entityManager -> {
List<Point> structs = entityManager.createQuery( "select b.thePoint from RecordStructHolder b where b.id = 1", Point.class ).getResultList();
assertEquals( 1, structs.size() );
assertStructEquals( Point.createAggregate1(), structs.get( 0 ) );
}
);
}
@Test
public void testSelectionItems() {
sessionFactoryScope().inSession(
entityManager -> {
List<Tuple> tuples = entityManager.createQuery(
"select " +
"b.thePoint.x," +
"b.thePoint.y," +
"b.thePoint.z " +
"from RecordStructHolder b where b.id = 1",
Tuple.class
).getResultList();
assertEquals( 1, tuples.size() );
final Tuple tuple = tuples.get( 0 );
assertStructEquals(
Point.createAggregate1(),
new Point(
tuple.get( 1, String.class ),
tuple.get( 2, long.class ),
tuple.get( 0, int.class )
)
);
}
);
}
@Test
public void testDeleteWhere() {
sessionFactoryScope().inTransaction(
entityManager -> {
entityManager.createQuery( "delete RecordStructHolder b where b.thePoint is not null" ).executeUpdate();
assertNull( entityManager.find( RecordStructHolder.class, 1L ) );
}
);
}
@Test
public void testUpdateAggregate() {
sessionFactoryScope().inTransaction(
entityManager -> {
entityManager.createQuery( "update RecordStructHolder b set b.thePoint = null" ).executeUpdate();
assertNull( entityManager.find( RecordStructHolder.class, 1L ).getThePoint() );
}
);
}
@Test
public void testUpdateAggregateMember() {
sessionFactoryScope().inTransaction(
entityManager -> {
entityManager.createQuery( "update RecordStructHolder b set b.thePoint.x = null" ).executeUpdate();
Point struct = Point.createAggregate1().withX( null );
assertStructEquals( struct, entityManager.find( RecordStructHolder.class, 1L ).getThePoint() );
}
);
}
@Test
public void testUpdateMultipleAggregateMembers() {
sessionFactoryScope().inTransaction(
entityManager -> {
entityManager.createQuery( "update RecordStructHolder b set b.thePoint.y = null, b.thePoint.z = 0" ).executeUpdate();
Point struct = Point.createAggregate1().withY( null ).withZ( 0 );
assertStructEquals( struct, entityManager.find( RecordStructHolder.class, 1L ).getThePoint() );
}
);
}
@Test
public void testUpdateAllAggregateMembers() {
sessionFactoryScope().inTransaction(
entityManager -> {
Point struct = Point.createAggregate1();
entityManager.createQuery(
"update RecordStructHolder b set " +
"b.thePoint.x = :x," +
"b.thePoint.y = :y," +
"b.thePoint.z = :z " +
"where b.id = 2"
)
.setParameter( "x", struct.getX() )
.setParameter( "y", struct.getY() )
.setParameter( "z", struct.getZ() )
.executeUpdate();
assertStructEquals( Point.createAggregate1(), entityManager.find( RecordStructHolder.class, 2L ).getThePoint() );
}
);
}
@Test
public void testNativeQuery() {
sessionFactoryScope().inTransaction(
entityManager -> {
//noinspection unchecked
List<Object> resultList = entityManager.createNativeQuery(
"select b.thePoint from RecordStructHolder b where b.id = 1",
Object.class
)
.getResultList();
assertEquals( 1, resultList.size() );
assertInstanceOf( Point.class, resultList.get( 0 ) );
Point struct = (Point) resultList.get( 0 );
assertStructEquals( Point.createAggregate1(), struct );
}
);
}
private void assertStructEquals(Point point1, Point point2) {
assertEquals( point1.getX(), point2.getX() );
assertEquals( point1.getY(), point2.getY() );
assertEquals( point1.getZ(), point2.getZ() );
}
@Entity(name = "RecordStructHolder")
public static class RecordStructHolder {
@Id
private Long id;
@Struct(name = "my_point_type")
private Point thePoint;
public RecordStructHolder() {
}
public RecordStructHolder(Long id, Point thePoint) {
this.id = id;
this.thePoint = thePoint;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Point getThePoint() {
return thePoint;
}
public void setThePoint(Point aggregate) {
this.thePoint = aggregate;
}
}
@Embeddable
public static class Point {
private final String y;
private final long z;
private final Integer x;
@Instantiator({"y","z","x"})
public Point(String y, long z, Integer x) {
this.y = y;
this.x = x;
this.z = z;
}
public String getY() {
return y;
}
public Integer getX() {
return x;
}
public long getZ() {
return z;
}
public Point withX(Integer x) {
return new Point( y, z, x );
}
public Point withY(String y) {
return new Point( y, z, x );
}
public Point withZ(long z) {
return new Point( y, z, x );
}
public static Point createAggregate1() {
return new Point( "1", -100, 10 );
}
public static Point createAggregate2() {
return new Point( "20", -200, 2 );
}
@Override
public boolean equals(Object o) {
if ( this == o ) {
return true;
}
if ( o == null || getClass() != o.getClass() ) {
return false;
}
Point point = (Point) o;
if ( !Objects.equals( y, point.y ) ) {
return false;
}
if ( !Objects.equals( x, point.x ) ) {
return false;
}
return Objects.equals( z, point.z );
}
@Override
public int hashCode() {
int result = y != null ? y.hashCode() : 0;
result = 31 * result + (int) ( z ^ ( z >>> 32 ) );
result = 31 * result + ( x != null ? x.hashCode() : 0 );
return result;
}
}
}

View File

@ -19,6 +19,7 @@ import org.hibernate.mapping.Component;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.Value;
import org.hibernate.metamodel.internal.EmbeddableCompositeUserTypeInstantiator;
import org.hibernate.metamodel.internal.EmbeddableInstantiatorPojoIndirecting;
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
import org.hibernate.resource.beans.spi.ManagedBeanRegistry;
import org.hibernate.usertype.CompositeUserType;
@ -69,6 +70,13 @@ public final class ComponentMetadataGenerator extends AbstractMetadataGenerator
.getBeanInstance();
instantiator = new EmbeddableCompositeUserTypeInstantiator( compositeUserType );
}
else if ( propComponent.getInstantiator() != null ) {
instantiator = EmbeddableInstantiatorPojoIndirecting.of(
propComponent.getPropertyNames(),
propComponent.getInstantiator(),
propComponent.getInstantiatorPropertyNames()
);
}
else {
instantiator = null;
}