HHH-15898 Add @Instantiator annotation to mark canonical constructor for embeddables
This commit is contained in:
parent
e19727e454
commit
ba985518c7
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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 ) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue