HHH-15831 Support non-basic values in aggregate components

This commit is contained in:
Christian Beikov 2024-05-15 18:42:30 +02:00
parent 6247dea9a6
commit ed2fdce0a6
88 changed files with 2651 additions and 594 deletions

View File

@ -407,6 +407,8 @@ public class CockroachLegacyDialect extends Dialect {
.getDescriptor( Object.class )
)
);
jdbcTypeRegistry.addTypeConstructor( PostgreSQLArrayJdbcTypeConstructor.INSTANCE );
}
@Override

View File

@ -1441,6 +1441,8 @@ public class PostgreSQLLegacyDialect extends Dialect {
.getDescriptor( Object.class )
)
);
jdbcTypeRegistry.addTypeConstructor( PostgreSQLArrayJdbcTypeConstructor.INSTANCE );
}
@Override

View File

@ -47,6 +47,16 @@ public @interface Struct {
*/
String name();
/** (Optional) The catalog of the UDT.
* <p> Defaults to the default catalog.
*/
String catalog() default "";
/** (Optional) The schema of the UDT.
* <p> Defaults to the default schema for user.
*/
String schema() default "";
/**
* The ordered set of attributes of the UDT, as they appear physically in the DDL.
* It is important to specify the attributes in the same order for JDBC interactions to work correctly.

View File

@ -1778,7 +1778,6 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector,
processSecondPasses( idGeneratorResolverSecondPassList );
processSecondPasses( implicitColumnNamingSecondPassList );
processSecondPasses( setBasicValueTypeSecondPassList );
processSecondPasses( aggregateComponentSecondPassList );
processSecondPasses( toOneJoinTableSecondPassList );
composites.forEach( Component::sortProperties );
@ -1794,6 +1793,7 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector,
processPropertyReferences();
processSecondPasses( aggregateComponentSecondPassList );
secondPassCompileForeignKeys( buildingContext );
processNaturalIdUniqueKeyBinders();

View File

@ -8,20 +8,20 @@ package org.hibernate.boot.model.internal;
import java.util.List;
import org.hibernate.AnnotationException;
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.annotations.Struct;
import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.boot.model.relational.Database;
import org.hibernate.boot.model.relational.QualifiedName;
import org.hibernate.boot.model.relational.QualifiedNameImpl;
import org.hibernate.boot.model.relational.QualifiedNameParser;
import org.hibernate.boot.spi.InFlightMetadataCollector;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.boot.spi.PropertyData;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.mapping.AggregateColumn;
import org.hibernate.mapping.BasicValue;
import org.hibernate.mapping.Component;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.Value;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.java.spi.EmbeddableAggregateJavaType;
import org.hibernate.type.spi.TypeConfiguration;
@ -42,19 +42,18 @@ public final class AggregateComponentBinder {
AnnotatedColumns columns,
MetadataBuildingContext context) {
if ( isAggregate( inferredData.getProperty(), componentXClass ) ) {
validateComponent( component, BinderHelper.getPath( propertyHolder, inferredData ) );
final InFlightMetadataCollector metadataCollector = context.getMetadataCollector();
final TypeConfiguration typeConfiguration = metadataCollector.getTypeConfiguration();
// Determine a struct name if this is a struct through some means
final String structName = determineStructName( columns, inferredData, componentXClass );
final QualifiedName structQualifiedName = determineStructName( columns, inferredData, componentXClass, context );
final String structName = structQualifiedName == null ? null : structQualifiedName.render();
// We must register a special JavaType for the embeddable which can provide a recommended JdbcType
typeConfiguration.getJavaTypeRegistry().resolveDescriptor(
component.getComponentClass(),
() -> new EmbeddableAggregateJavaType<>( component.getComponentClass(), structName )
);
component.setStructName( structName );
component.setStructName( structQualifiedName );
component.setStructColumnNames( determineStructAttributeNames( inferredData, componentXClass ) );
// Determine the aggregate column
@ -97,6 +96,7 @@ public final class AggregateComponentBinder {
propertyHolder,
component,
componentXClass,
inferredData.getPropertyName(),
context
)
);
@ -111,60 +111,59 @@ public final class AggregateComponentBinder {
case SqlTypes.TABLE:
return SqlTypes.STRUCT_TABLE;
default:
throw new IllegalArgumentException( "Unsupported array type code: " + arrayTypeCode );
throw new UnsupportedOperationException( "Dialect does not support structured array types: " + context.getMetadataCollector()
.getDatabase()
.getDialect()
.getClass()
.getName() );
}
}
private static void validateComponent(Component component, String basePath) {
for ( Property property : component.getProperties() ) {
final Value value = property.getValue();
if ( !( value instanceof BasicValue ) && !( value instanceof Component ) ) {
// todo: see HHH-15831
throw new AnnotationException(
"Property '" + StringHelper.qualify( basePath, property.getName() )
+ "' uses not yet supported mapping type '"
+ value.getClass().getName()
+ "' in component class '"
+ component.getComponentClassName()
+ "'. Aggregate components currently may only contain basic values and components of basic values."
);
}
if ( value instanceof Component ) {
final Component c = (Component) value;
if ( c.getAggregateColumn() == null ) {
validateComponent( c, StringHelper.qualify( basePath, property.getName() ) );
}
}
}
}
private static String determineStructName(
private static QualifiedName determineStructName(
AnnotatedColumns columns,
PropertyData inferredData,
XClass returnedClassOrElement) {
XClass returnedClassOrElement,
MetadataBuildingContext context) {
final XProperty property = inferredData.getProperty();
if ( property != null ) {
final Struct struct = property.getAnnotation( Struct.class );
if ( struct != null ) {
return struct.name();
return toQualifiedName( struct, context );
}
final JdbcTypeCode jdbcTypeCode = property.getAnnotation( JdbcTypeCode.class );
if ( jdbcTypeCode != null
&& ( jdbcTypeCode.value() == SqlTypes.STRUCT || jdbcTypeCode.value() == SqlTypes.STRUCT_ARRAY || jdbcTypeCode.value() == SqlTypes.STRUCT_TABLE )
&& columns != null ) {
final List<AnnotatedColumn> columnList = columns.getColumns();
if ( columnList.size() == 1 && columnList.get( 0 ).getSqlType() != null ) {
return columnList.get( 0 ).getSqlType();
final String sqlType;
if ( columnList.size() == 1 && ( sqlType = columnList.get( 0 ).getSqlType() ) != null ) {
if ( sqlType.contains( "." ) ) {
return QualifiedNameParser.INSTANCE.parse( sqlType );
}
return new QualifiedNameParser.NameParts(
null,
null,
context.getMetadataCollector().getDatabase().toIdentifier( sqlType )
);
}
}
}
final Struct struct = returnedClassOrElement.getAnnotation( Struct.class );
if ( struct != null ) {
return struct.name();
return toQualifiedName( struct, context );
}
return null;
}
private static QualifiedName toQualifiedName(Struct struct, MetadataBuildingContext context) {
final Database database = context.getMetadataCollector().getDatabase();
return new QualifiedNameImpl(
database.toIdentifier( struct.catalog() ),
database.toIdentifier( struct.schema() ),
database.toIdentifier( struct.name() )
);
}
private static String[] determineStructAttributeNames(PropertyData inferredData, XClass returnedClassOrElement) {
final XProperty property = inferredData.getProperty();
if ( property != null ) {

View File

@ -11,25 +11,27 @@ import java.util.List;
import java.util.Map;
import java.util.TreeSet;
import org.hibernate.AnnotationException;
import org.hibernate.MappingException;
import org.hibernate.annotations.Comment;
import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.model.relational.Database;
import org.hibernate.boot.model.relational.Namespace;
import org.hibernate.boot.model.relational.QualifiedName;
import org.hibernate.boot.spi.InFlightMetadataCollector;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.boot.spi.SecondPass;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.aggregate.AggregateSupport;
import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.mapping.AggregateColumn;
import org.hibernate.mapping.Collection;
import org.hibernate.mapping.Column;
import org.hibernate.mapping.Component;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.Selectable;
import org.hibernate.mapping.ToOne;
import org.hibernate.mapping.UserDefinedObjectType;
import org.hibernate.mapping.Value;
import org.hibernate.metamodel.internal.EmbeddableHelper;
@ -37,6 +39,8 @@ import org.hibernate.sql.Template;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.spi.TypeConfiguration;
import static org.hibernate.internal.util.StringHelper.qualify;
/**
* @author Christian Beikov
*/
@ -45,20 +49,26 @@ public class AggregateComponentSecondPass implements SecondPass {
private final PropertyHolder propertyHolder;
private final Component component;
private final XClass componentXClass;
private final String propertyName;
private final MetadataBuildingContext context;
public AggregateComponentSecondPass(
PropertyHolder propertyHolder,
Component component,
XClass componentXClass,
String propertyName,
MetadataBuildingContext context) {
this.propertyHolder = propertyHolder;
this.component = component;
this.componentXClass = componentXClass;
this.propertyName = propertyName;
this.context = context;
}
@Override
public void doSecondPass(Map<String, PersistentClass> persistentClasses) throws MappingException {
validateComponent( component, qualify( propertyHolder.getPath(), propertyName ), isAggregateArray() );
final InFlightMetadataCollector metadataCollector = context.getMetadataCollector();
final TypeConfiguration typeConfiguration = metadataCollector.getTypeConfiguration();
final Database database = metadataCollector.getDatabase();
@ -72,21 +82,17 @@ public class AggregateComponentSecondPass implements SecondPass {
final List<Column> aggregatedColumns = component.getAggregatedColumns();
final AggregateColumn aggregateColumn = component.getAggregateColumn();
ensureInitialized( metadataCollector, typeConfiguration, dialect, aggregateColumn );
ensureInitialized( metadataCollector, aggregateColumn );
validateSupportedColumnTypes( propertyHolder.getPath(), component );
for ( org.hibernate.mapping.Column aggregatedColumn : aggregatedColumns ) {
// Make sure this state is initialized
aggregatedColumn.getSqlTypeCode( metadataCollector );
aggregatedColumn.getSqlType( metadataCollector );
}
final String structName = component.getStructName();
final QualifiedName structName = component.getStructName();
final boolean addAuxiliaryObjects;
if ( structName != null ) {
final Namespace defaultNamespace = database.getDefaultNamespace();
final Identifier udtName = Identifier.toIdentifier( structName );
final UserDefinedObjectType udt = new UserDefinedObjectType( "orm", defaultNamespace, udtName );
final Namespace namespace = database.locateNamespace(
structName.getCatalogName(),
structName.getSchemaName()
);
final UserDefinedObjectType udt = new UserDefinedObjectType( "orm", namespace, structName.getObjectName() );
final Comment comment = componentXClass.getAnnotation( Comment.class );
if ( comment != null ) {
udt.setComment( comment.value() );
@ -94,8 +100,8 @@ public class AggregateComponentSecondPass implements SecondPass {
for ( org.hibernate.mapping.Column aggregatedColumn : aggregatedColumns ) {
udt.addColumn( aggregatedColumn );
}
final UserDefinedObjectType registeredUdt = defaultNamespace.createUserDefinedType(
udtName,
final UserDefinedObjectType registeredUdt = namespace.createUserDefinedType(
structName.getObjectName(),
name -> udt
);
if ( registeredUdt == udt ) {
@ -187,6 +193,66 @@ public class AggregateComponentSecondPass implements SecondPass {
propertyHolder.getTable().getColumns().removeAll( aggregatedColumns );
}
private static void validateComponent(Component component, String basePath, boolean inArray) {
for ( Property property : component.getProperties() ) {
final Value value = property.getValue();
if ( value instanceof Component ) {
final Component c = (Component) value;
validateComponent( c, qualify( basePath, property.getName() ), inArray );
}
else if ( value instanceof ToOne ) {
final ToOne toOne = (ToOne) value;
if ( inArray && toOne.getReferencedPropertyName() != null ) {
throw new AnnotationException(
"Property '" + qualify( basePath, property.getName() )
+ "' uses one-to-one mapping with mappedBy '"
+ toOne.getReferencedPropertyName()
+ "' in the aggregate component class '"
+ component.getComponentClassName()
+ "' within an array property, which is not allowed."
);
}
}
else if ( value instanceof Collection ) {
final Collection collection = (Collection) value;
if ( inArray && collection.getMappedByProperty() != null ) {
throw new AnnotationException(
"Property '" + qualify( basePath, property.getName() )
+ "' uses *-to-many mapping with mappedBy '"
+ collection.getMappedByProperty()
+ "' in the aggregate component class '"
+ component.getComponentClassName()
+ "' within an array property, which is not allowed."
);
}
if ( inArray && collection.getCollectionTable() != null ) {
throw new AnnotationException(
"Property '" + qualify( basePath, property.getName() )
+ "' defines a collection table '"
+ collection.getCollectionTable()
+ "' in the aggregate component class '"
+ component.getComponentClassName()
+ "' within an array property, which is not allowed."
);
}
}
}
}
private boolean isAggregateArray() {
switch ( component.getAggregateColumn().getSqlTypeCode( context.getMetadataCollector() ) ) {
case SqlTypes.STRUCT_ARRAY:
case SqlTypes.STRUCT_TABLE:
case SqlTypes.JSON_ARRAY:
case SqlTypes.XML_ARRAY:
case SqlTypes.ARRAY:
case SqlTypes.TABLE:
return true;
default:
return false;
}
}
private void orderColumns(UserDefinedObjectType userDefinedType, int[] originalOrder) {
final Class<?> componentClass = component.getComponentClass();
final String[] structColumnNames = component.getStructColumnNames();
@ -303,7 +369,7 @@ public class AggregateComponentSecondPass implements SecondPass {
if ( value instanceof Component ) {
final Component subComponent = (Component) value;
if ( subComponent.getAggregateColumn() == null ) {
validateSupportedColumnTypes( StringHelper.qualify( basePath, property.getName() ), subComponent );
validateSupportedColumnTypes( qualify( basePath, property.getName() ), subComponent );
}
}
}
@ -311,8 +377,27 @@ public class AggregateComponentSecondPass implements SecondPass {
private static void ensureInitialized(
InFlightMetadataCollector metadataCollector,
TypeConfiguration typeConfiguration,
Dialect dialect,
AggregateColumn aggregateColumn) {
ensureParentInitialized( metadataCollector, aggregateColumn );
ensureChildrenInitialized( metadataCollector, aggregateColumn );
}
private static void ensureChildrenInitialized(
InFlightMetadataCollector metadataCollector,
AggregateColumn aggregateColumn) {
for ( Column aggregatedColumn : aggregateColumn.getComponent().getAggregatedColumns() ) {
// Make sure this state is initialized
aggregatedColumn.getSqlTypeCode( metadataCollector );
aggregatedColumn.getSqlType( metadataCollector );
if ( aggregatedColumn instanceof AggregateColumn ) {
ensureChildrenInitialized( metadataCollector, (AggregateColumn) aggregatedColumn );
}
}
}
private static void ensureParentInitialized(
InFlightMetadataCollector metadataCollector,
AggregateColumn aggregateColumn) {
do {
// Trigger resolving of the value so that the column gets properly filled

View File

@ -1224,6 +1224,11 @@ public class BasicValueBinder implements JdbcTypeIndicators {
basicValue = new BasicValue( buildingContext, table );
if ( columns.getPropertyHolder().isComponent() ) {
final ComponentPropertyHolder propertyHolder = (ComponentPropertyHolder) columns.getPropertyHolder();
basicValue.setAggregateColumn( propertyHolder.getAggregateColumn() );
}
if ( isNationalized() ) {
basicValue.makeNationalized();
}

View File

@ -14,6 +14,7 @@ import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.boot.spi.PropertyData;
import org.hibernate.mapping.AggregateColumn;
import org.hibernate.mapping.Component;
import org.hibernate.mapping.Join;
import org.hibernate.mapping.KeyValue;
@ -265,7 +266,7 @@ public class ComponentPropertyHolder extends AbstractPropertyHolder {
// if a property is set already the core cannot support that
if ( columns != null ) {
final Table table = columns.getTable();
if ( !table.equals( component.getTable() ) ) {
if ( !table.equals( getTable() ) ) {
if ( component.getPropertySpan() == 0 ) {
component.setTable( table );
}
@ -301,6 +302,11 @@ public class ComponentPropertyHolder extends AbstractPropertyHolder {
return component.getOwner().getClassName();
}
public AggregateColumn getAggregateColumn() {
final AggregateColumn aggregateColumn = component.getAggregateColumn();
return aggregateColumn != null ? aggregateColumn : component.getParentAggregateColumn();
}
@Override
public Table getTable() {
return component.getTable();

View File

@ -28,6 +28,7 @@ import org.hibernate.boot.spi.AccessType;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.boot.spi.PropertyData;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.mapping.AggregateColumn;
import org.hibernate.mapping.BasicValue;
import org.hibernate.mapping.Component;
import org.hibernate.mapping.Property;
@ -371,6 +372,14 @@ public class EmbeddableBinder {
returnedClassOrElement = context.getBootstrapContext().getReflectionManager()
.toXClass( compositeUserType.embeddable() );
}
AggregateComponentBinder.processAggregate(
component,
propertyHolder,
inferredData,
returnedClassOrElement,
columns,
context
);
final XClass annotatedClass = inferredData.getPropertyClass();
final List<PropertyData> classElements =
@ -447,14 +456,6 @@ public class EmbeddableBinder {
processCompositeUserType( component, compositeUserType );
}
AggregateComponentBinder.processAggregate(
component,
propertyHolder,
inferredData,
returnedClassOrElement,
columns,
context
);
return component;
}
@ -563,6 +564,7 @@ public class EmbeddableBinder {
columns.setBuildingContext( context );
discriminatorColumn.setParent( columns );
final BasicValue discriminatorColumnBinding = new BasicValue( context, component.getTable() );
discriminatorColumnBinding.setAggregateColumn( component.getAggregateColumn() );
component.setDiscriminator( discriminatorColumnBinding );
discriminatorColumn.linkWithValue( discriminatorColumnBinding );
discriminatorColumnBinding.setTypeName( discriminatorColumn.getDiscriminatorTypeName() );
@ -858,6 +860,10 @@ public class EmbeddableBinder {
if ( constructor != null ) {
component.setInstantiator( constructor, constructor.getAnnotation( Instantiator.class ).value() );
}
if ( propertyHolder.isComponent() ) {
final ComponentPropertyHolder componentPropertyHolder = (ComponentPropertyHolder) propertyHolder;
component.setParentAggregateColumn( componentPropertyHolder.getAggregateColumn() );
}
return component;
}

View File

@ -34,6 +34,7 @@ import org.hibernate.mapping.UserDefinedArrayType;
import org.hibernate.mapping.UserDefinedObjectType;
import org.hibernate.mapping.UserDefinedType;
import org.hibernate.type.BasicType;
import org.hibernate.type.Type;
import org.hibernate.type.descriptor.jdbc.ArrayJdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.SqlTypedJdbcType;
@ -183,7 +184,9 @@ public class Namespace {
final UserDefinedType udt = entry.getValue();
if ( udt instanceof UserDefinedObjectType ) {
for ( Column udtColumn : ( (UserDefinedObjectType) udt ).getColumns() ) {
final JdbcType jdbcType = ( (BasicType<?>) udtColumn.getValue().getType() ).getJdbcType();
final Type udtColumnType = udtColumn.getValue().getType();
if ( udtColumnType instanceof BasicType<?> ) {
final JdbcType jdbcType = ( (BasicType<?>) udtColumnType ).getJdbcType();
if ( jdbcType instanceof SqlTypedJdbcType ) {
dependencies.add( Identifier.toIdentifier( ( (SqlTypedJdbcType) jdbcType ).getSqlTypeName() ) );
}
@ -194,6 +197,7 @@ public class Namespace {
}
}
}
}
if ( dependencies.isEmpty() ) {
// The UDTs without dependencies are added directly
orderedUdts.put( udt.getNameIdentifier(), udt );

View File

@ -24,13 +24,11 @@ import java.util.ArrayList;
import java.util.TimeZone;
import org.hibernate.internal.util.CharSequenceHelper;
import org.hibernate.metamodel.mapping.BasicValuedMapping;
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.mapping.SelectableMapping;
import org.hibernate.metamodel.mapping.ValuedModelPart;
import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping;
import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.sql.ast.spi.StringBuilderSqlAppender;
import org.hibernate.type.BasicPluralType;
@ -1008,7 +1006,7 @@ public abstract class AbstractPostgreSQLStructJdbcType implements StructJdbcType
final int size = numberOfAttributeMappings + ( embeddableMappingType.isPolymorphic() ? 1 : 0 );
int count = 0;
for ( int i = 0; i < size; i++ ) {
final ValuedModelPart modelPart = getEmbeddedPart( embeddableMappingType, numberOfAttributeMappings, orderMapping[i] );
final ValuedModelPart modelPart = getEmbeddedPart( embeddableMappingType, orderMapping[i] );
final MappingType mappedType = modelPart.getMappedType();
if ( mappedType instanceof EmbeddableMappingType ) {
final EmbeddableMappingType embeddableMappingType = (EmbeddableMappingType) mappedType;
@ -1178,7 +1176,7 @@ public abstract class AbstractPostgreSQLStructJdbcType implements StructJdbcType
return rawJdbcValue.toString();
}
protected <X> String toString(X value, JavaType<X> javaType, WrapperOptions options) {
protected <X> String toString(X value, JavaType<X> javaType, WrapperOptions options) throws SQLException {
if ( value == null ) {
return null;
}
@ -1187,86 +1185,62 @@ public abstract class AbstractPostgreSQLStructJdbcType implements StructJdbcType
return sb.toString();
}
private void serializeStructTo(PostgreSQLAppender appender, Object value, WrapperOptions options) {
serializeValuesTo( appender, options, embeddableMappingType, value, '(' );
private void serializeStructTo(PostgreSQLAppender appender, Object value, WrapperOptions options) throws SQLException {
serializeDomainValueTo( appender, options, value, '(' );
appender.append( ')' );
}
private void serializeValuesTo(
private void serializeDomainValueTo(
PostgreSQLAppender appender,
WrapperOptions options,
EmbeddableMappingType embeddableMappingType,
Object domainValue,
char separator) {
final Object[] array = embeddableMappingType.getValues( domainValue );
final int numberOfAttributes = embeddableMappingType.getNumberOfAttributeMappings();
for ( int i = 0; i < array.length; i++ ) {
final ValuedModelPart attributeMapping;
final Object attributeValue;
if ( orderMapping == null ) {
attributeMapping = getEmbeddedPart( embeddableMappingType, numberOfAttributes, i );
attributeValue = array[i];
}
else {
attributeMapping = getEmbeddedPart( embeddableMappingType, numberOfAttributes, orderMapping[i] );
attributeValue = array[orderMapping[i]];
}
if ( attributeMapping instanceof BasicValuedMapping ) {
appender.append( separator );
separator = ',';
if ( attributeValue == null ) {
continue;
}
final JdbcMapping jdbcMapping = ( (BasicValuedMapping) attributeMapping ).getJdbcMapping();
serializeBasicTo( appender, options, jdbcMapping, attributeValue );
}
else if ( attributeMapping instanceof EmbeddedAttributeMapping ) {
final EmbeddableMappingType mappingType = (EmbeddableMappingType) attributeMapping.getMappedType();
final SelectableMapping aggregateMapping = mappingType.getAggregateMapping();
if ( aggregateMapping == null ) {
serializeValuesTo(
char separator) throws SQLException {
serializeJdbcValuesTo(
appender,
options,
mappingType,
attributeValue,
StructHelper.getJdbcValues( embeddableMappingType, orderMapping, domainValue, options ),
separator
);
separator = ',';
}
else {
appender.append( separator );
separator = ',';
if ( attributeValue == null ) {
continue;
}
appender.quoteStart();
( (AbstractPostgreSQLStructJdbcType) aggregateMapping.getJdbcMapping().getJdbcType() ).serializeStructTo(
appender,
attributeValue,
options
);
appender.quoteEnd();
}
}
else {
throw new UnsupportedOperationException( "Unsupported attribute mapping: " + attributeMapping );
}
}
}
private void serializeBasicTo(
private void serializeJdbcValuesTo(
PostgreSQLAppender appender,
WrapperOptions options,
JdbcMapping jdbcMapping,
Object value) {
serializeConvertedBasicTo( appender, options, jdbcMapping, jdbcMapping.convertToRelationalValue( value ) );
Object[] jdbcValues,
char separator) throws SQLException {
for ( int i = 0; i < jdbcValues.length; i++ ) {
appender.append( separator );
separator = ',';
final Object jdbcValue = jdbcValues[i];
if ( jdbcValue == null ) {
continue;
}
final SelectableMapping selectableMapping = orderMapping == null ?
embeddableMappingType.getJdbcValueSelectable( i ) :
embeddableMappingType.getJdbcValueSelectable( orderMapping[i] );
final JdbcMapping jdbcMapping = selectableMapping.getJdbcMapping();
if ( jdbcMapping.getJdbcType() instanceof AbstractPostgreSQLStructJdbcType ) {
appender.quoteStart();
( (AbstractPostgreSQLStructJdbcType) jdbcMapping.getJdbcType() ).serializeJdbcValuesTo(
appender,
options,
(Object[]) jdbcValue,
'('
);
appender.append( ')' );
appender.quoteEnd();
}
else {
serializeConvertedBasicTo( appender, options, jdbcMapping, jdbcValue );
}
}
}
private void serializeConvertedBasicTo(
PostgreSQLAppender appender,
WrapperOptions options,
JdbcMapping jdbcMapping,
Object subValue) {
Object subValue) throws SQLException {
//noinspection unchecked
final JavaType<Object> jdbcJavaType = (JavaType<Object>) jdbcMapping.getJdbcJavaType();
switch ( jdbcMapping.getJdbcType().getDefaultSqlTypeCode() ) {
@ -1291,14 +1265,7 @@ public abstract class AbstractPostgreSQLStructJdbcType implements StructJdbcType
case SqlTypes.DECIMAL:
case SqlTypes.NUMERIC:
case SqlTypes.DURATION:
jdbcJavaType.appendEncodedString(
appender,
jdbcJavaType.unwrap(
subValue,
jdbcJavaType.getJavaTypeClass(),
options
)
);
appender.append( subValue.toString() );
break;
case SqlTypes.CHAR:
case SqlTypes.NCHAR:
@ -1316,14 +1283,7 @@ public abstract class AbstractPostgreSQLStructJdbcType implements StructJdbcType
case SqlTypes.ENUM:
case SqlTypes.NAMED_ENUM:
appender.quoteStart();
jdbcJavaType.appendEncodedString(
appender,
jdbcJavaType.unwrap(
subValue,
jdbcJavaType.getJavaTypeClass(),
options
)
);
appender.append( (String) subValue );
appender.quoteEnd();
break;
case SqlTypes.DATE:
@ -1393,9 +1353,8 @@ public abstract class AbstractPostgreSQLStructJdbcType implements StructJdbcType
case SqlTypes.STRUCT:
if ( subValue != null ) {
final AbstractPostgreSQLStructJdbcType structJdbcType = (AbstractPostgreSQLStructJdbcType) jdbcMapping.getJdbcType();
final EmbeddableMappingType subEmbeddableMappingType = structJdbcType.getEmbeddableMappingType();
appender.quoteStart();
structJdbcType.serializeValuesTo( appender, options, subEmbeddableMappingType, subValue, '(' );
structJdbcType.serializeJdbcValuesTo( appender, options, (Object[]) subValue, '(' );
appender.append( ')' );
appender.quoteEnd();
}
@ -1427,9 +1386,8 @@ public abstract class AbstractPostgreSQLStructJdbcType implements StructJdbcType
else {
attributeIndex = orderMapping[i];
}
final ValuedModelPart modelPart = getEmbeddedPart( embeddableMappingType, numberOfAttributeMappings, attributeIndex );
jdbcIndex += injectAttributeValue(
modelPart,
getEmbeddedPart( embeddableMappingType, attributeIndex ),
attributeValues,
attributeIndex,
rawJdbcValues,
@ -1567,6 +1525,10 @@ public abstract class AbstractPostgreSQLStructJdbcType implements StructJdbcType
: options.getJdbcTimeZone();
}
protected <X> Object getBindValue(X value, WrapperOptions options) throws SQLException {
return StructHelper.getJdbcValues( embeddableMappingType, orderMapping, value, options );
}
private static class PostgreSQLAppender extends StringBuilderSqlAppender {
private int quote = 1;

View File

@ -410,6 +410,8 @@ public class CockroachDialect extends Dialect {
.getDescriptor( Object.class )
)
);
jdbcTypeRegistry.addTypeConstructor( PostgreSQLArrayJdbcTypeConstructor.INSTANCE );
}
@Override

View File

@ -73,7 +73,7 @@ public class JsonHelper {
final Object[] values = embeddableMappingType.getValues( domainValue );
final int numberOfAttributes = embeddableMappingType.getNumberOfAttributeMappings();
for ( int i = 0; i < values.length; i++ ) {
final ValuedModelPart attributeMapping = getEmbeddedPart( embeddableMappingType, numberOfAttributes, i );
final ValuedModelPart attributeMapping = getEmbeddedPart( embeddableMappingType, i );
if ( attributeMapping instanceof SelectableMapping ) {
final String name = ( (SelectableMapping) attributeMapping ).getSelectableName();
appender.append( separator );

View File

@ -69,6 +69,8 @@ public class OracleArrayJdbcType extends ArrayJdbcType implements SqlTypedJdbcTy
@Override
public <X> ValueBinder<X> getBinder(final JavaType<X> javaTypeDescriptor) {
//noinspection unchecked
final ValueBinder<Object> elementBinder = getElementJdbcType().getBinder( ( (BasicPluralJavaType<Object>) javaTypeDescriptor ).getElementJavaType() );
return new BasicBinder<>( javaTypeDescriptor, this ) {
private String typeName(WrapperOptions options) {
return ( upperTypeName == null
@ -105,7 +107,7 @@ public class OracleArrayJdbcType extends ArrayJdbcType implements SqlTypedJdbcTy
@Override
public java.sql.Array getBindValue(X value, WrapperOptions options) throws SQLException {
final Object[] objects = OracleArrayJdbcType.this.getArray( this, value, options );
final Object[] objects = ( (OracleArrayJdbcType) getJdbcType() ).getArray( this, elementBinder, value, options );
final String arrayTypeName = typeName( options );
final OracleConnection oracleConnection = options.getSession()

View File

@ -6,6 +6,9 @@
*/
package org.hibernate.dialect;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Locale;
import org.hibernate.HibernateException;
@ -17,9 +20,12 @@ import org.hibernate.metamodel.mapping.EmbeddableMappingType;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.SelectableMapping;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
import org.hibernate.type.Type;
import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.AggregateJdbcType;
import org.hibernate.type.descriptor.jdbc.BasicBinder;
import oracle.sql.TIMESTAMPTZ;
@ -41,6 +47,28 @@ public class OracleBaseStructJdbcType extends StructJdbcType {
);
}
@Override
public <X> ValueBinder<X> getBinder(JavaType<X> javaType) {
return new BasicBinder<>( javaType, this ) {
@Override
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options)
throws SQLException {
st.setObject( index, createJdbcValue( value, options ) );
}
@Override
protected void doBind(CallableStatement st, X value, String name, WrapperOptions options)
throws SQLException {
st.setObject( name, createJdbcValue( value, options ) );
}
@Override
public Object getBindValue(X value, WrapperOptions options) throws SQLException {
return createJdbcValue( value, options );
}
};
}
@Override
public String getExtraCreateTableInfo(
JavaType<?> javaType,
@ -51,7 +79,9 @@ public class OracleBaseStructJdbcType extends StructJdbcType {
.locateUserDefinedType( Identifier.toIdentifier( getSqlTypeName() ) );
StringBuilder sb = null;
for ( Column column : udt.getColumns() ) {
final JdbcMapping jdbcMapping = (JdbcMapping) column.getValue().getType();
final Type columnType = column.getValue().getType();
if ( columnType instanceof JdbcMapping ) {
final JdbcMapping jdbcMapping = (JdbcMapping) columnType;
final String extraCreateTableInfo = jdbcMapping.getJdbcType().getExtraCreateTableInfo(
jdbcMapping.getJavaTypeDescriptor(),
columnName + "." + column.getName(),
@ -68,6 +98,7 @@ public class OracleBaseStructJdbcType extends StructJdbcType {
sb.append( extraCreateTableInfo );
}
}
}
return sb != null ? sb.toString() : "";
}
}

View File

@ -0,0 +1,97 @@
/*
* 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.dialect;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Types;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.BasicPluralJavaType;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.AggregateJdbcType;
import org.hibernate.type.descriptor.jdbc.ArrayJdbcType;
import org.hibernate.type.descriptor.jdbc.BasicBinder;
import org.hibernate.type.descriptor.jdbc.JdbcType;
/**
* Descriptor for {@link Types#ARRAY ARRAY} handling.
*/
public class PostgreSQLArrayJdbcType extends ArrayJdbcType {
public PostgreSQLArrayJdbcType(JdbcType elementJdbcType) {
super( elementJdbcType );
}
@Override
public <X> ValueBinder<X> getBinder(final JavaType<X> javaTypeDescriptor) {
//noinspection unchecked
final ValueBinder<Object> elementBinder = getElementJdbcType().getBinder( ( (BasicPluralJavaType<Object>) javaTypeDescriptor ).getElementJavaType() );
return new BasicBinder<>( javaTypeDescriptor, this ) {
@Override
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException {
st.setArray( index, getArray( value, options ) );
}
@Override
protected void doBind(CallableStatement st, X value, String name, WrapperOptions options)
throws SQLException {
final java.sql.Array arr = getArray( value, options );
try {
st.setObject( name, arr, java.sql.Types.ARRAY );
}
catch (SQLException ex) {
throw new HibernateException( "JDBC driver does not support named parameters for setArray. Use positional.", ex );
}
}
@Override
public Object getBindValue(X value, WrapperOptions options) throws SQLException {
return ( (PostgreSQLArrayJdbcType) getJdbcType() ).getArray( this, elementBinder, value, options );
}
private java.sql.Array getArray(X value, WrapperOptions options) throws SQLException {
final PostgreSQLArrayJdbcType arrayJdbcType = (PostgreSQLArrayJdbcType) getJdbcType();
final Object[] objects;
final JdbcType elementJdbcType = arrayJdbcType.getElementJdbcType();
if ( elementJdbcType instanceof AggregateJdbcType ) {
// The PostgreSQL JDBC driver does not support arrays of structs, which contain byte[]
final AggregateJdbcType aggregateJdbcType = (AggregateJdbcType) elementJdbcType;
final Object[] domainObjects = getJavaType().unwrap(
value,
Object[].class,
options
);
objects = new Object[domainObjects.length];
for ( int i = 0; i < domainObjects.length; i++ ) {
objects[i] = aggregateJdbcType.createJdbcValue( domainObjects[i], options );
}
}
else {
objects = arrayJdbcType.getArray( this, elementBinder, value, options );
}
final SharedSessionContractImplementor session = options.getSession();
final String typeName = arrayJdbcType.getElementTypeName( getJavaType(), session );
return session.getJdbcCoordinator().getLogicalConnection().getPhysicalConnection()
.createArrayOf( typeName, objects );
}
};
}
@Override
public String toString() {
return "PostgreSQLArrayTypeDescriptor(" + getElementJdbcType().toString() + ")";
}
}

View File

@ -0,0 +1,46 @@
/*
* 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.dialect;
import java.sql.Types;
import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation;
import org.hibernate.type.BasicType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeConstructor;
import org.hibernate.type.spi.TypeConfiguration;
/**
* Factory for {@link PostgreSQLArrayJdbcType}.
*/
public class PostgreSQLArrayJdbcTypeConstructor implements JdbcTypeConstructor {
public static final PostgreSQLArrayJdbcTypeConstructor INSTANCE = new PostgreSQLArrayJdbcTypeConstructor();
@Override
public JdbcType resolveType(
TypeConfiguration typeConfiguration,
Dialect dialect,
BasicType<?> elementType,
ColumnTypeInformation columnTypeInformation) {
return resolveType( typeConfiguration, dialect, elementType.getJdbcType(), columnTypeInformation );
}
@Override
public JdbcType resolveType(
TypeConfiguration typeConfiguration,
Dialect dialect,
JdbcType elementType,
ColumnTypeInformation columnTypeInformation) {
return new PostgreSQLArrayJdbcType( elementType );
}
@Override
public int getDefaultSqlTypeCode() {
return Types.ARRAY;
}
}

View File

@ -1474,6 +1474,8 @@ public class PostgreSQLDialect extends Dialect {
jdbcTypeRegistry.addDescriptor( PostgreSQLEnumJdbcType.INSTANCE );
jdbcTypeRegistry.addDescriptor( PostgreSQLOrdinalEnumJdbcType.INSTANCE );
jdbcTypeRegistry.addDescriptor( PostgreSQLUUIDJdbcType.INSTANCE );
jdbcTypeRegistry.addTypeConstructor( PostgreSQLArrayJdbcTypeConstructor.INSTANCE );
}
@Override

View File

@ -89,6 +89,11 @@ public class PostgreSQLStructCastingJdbcType extends AbstractPostgreSQLStructJdb
);
st.setString( name, stringValue );
}
@Override
public Object getBindValue(X value, WrapperOptions options) throws SQLException {
return ( (PostgreSQLStructCastingJdbcType) getJdbcType() ).getBindValue( value, options );
}
};
}
}

View File

@ -97,6 +97,11 @@ public class PostgreSQLStructPGObjectJdbcType extends AbstractPostgreSQLStructJd
holder.setValue( stringValue );
st.setObject( name, holder );
}
@Override
public Object getBindValue(X value, WrapperOptions options) throws SQLException {
return ( (PostgreSQLStructPGObjectJdbcType) getJdbcType() ).getBindValue( value, options );
}
};
}

View File

@ -14,12 +14,18 @@ import java.sql.SQLException;
import org.hibernate.Internal;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.BasicValuedMapping;
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.ValuedModelPart;
import org.hibernate.metamodel.mapping.internal.DiscriminatedAssociationAttributeMapping;
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
import org.hibernate.metamodel.spi.EmbeddableRepresentationStrategy;
import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.JavaType;
@ -39,12 +45,14 @@ public class StructHelper {
final StructAttributeValues attributeValues = new StructAttributeValues( numberOfAttributeMappings, rawJdbcValues );
int jdbcIndex = 0;
for ( int i = 0; i < size; i++ ) {
final ValuedModelPart valuedModelPart = getEmbeddedPart(
embeddableMappingType,
numberOfAttributeMappings,
i
jdbcIndex += injectAttributeValue(
getEmbeddedPart( embeddableMappingType, i ),
attributeValues,
i,
rawJdbcValues,
jdbcIndex,
options
);
jdbcIndex += injectAttributeValue( valuedModelPart, attributeValues, i, rawJdbcValues, jdbcIndex, options );
}
return attributeValues;
}
@ -104,26 +112,58 @@ public class StructHelper {
else {
jdbcValues = values;
}
int jdbcIndex = 0;
for ( int i = 0; i < values.length; i++ ) {
final int attributeIndex;
if ( orderMapping == null ) {
attributeIndex = i;
}
else {
attributeIndex = orderMapping[i];
}
jdbcIndex += injectJdbcValue(
getEmbeddedPart( embeddableMappingType, jdbcValueCount, attributeIndex ),
injectJdbcValues(
embeddableMappingType,
values,
attributeIndex,
jdbcValues,
0,
options
);
if ( orderMapping != null ) {
final Object[] originalJdbcValues = jdbcValues.clone();
for ( int i = 0; i < orderMapping.length; i++ ) {
jdbcValues[i] = originalJdbcValues[orderMapping[i]];
}
}
return jdbcValues;
}
private static int injectJdbcValues(
EmbeddableMappingType embeddableMappingType,
Object domainValue,
Object[] jdbcValues,
int jdbcIndex,
WrapperOptions options) throws SQLException {
return injectJdbcValues(
embeddableMappingType,
embeddableMappingType.getValues( domainValue ),
jdbcValues,
jdbcIndex,
options
);
}
assert jdbcIndex == valueCount;
return jdbcValues;
private static int injectJdbcValues(
EmbeddableMappingType embeddableMappingType,
Object[] values,
Object[] jdbcValues,
int jdbcIndex,
WrapperOptions options) throws SQLException {
final int jdbcValueCount = embeddableMappingType.getJdbcValueCount();
final int valueCount = jdbcValueCount + ( embeddableMappingType.isPolymorphic() ? 1 : 0 );
int offset = 0;
for ( int i = 0; i < values.length; i++ ) {
offset += injectJdbcValue(
getEmbeddedPart( embeddableMappingType, i ),
values,
i,
jdbcValues,
jdbcIndex + offset,
options
);
}
assert offset == valueCount;
return offset;
}
public static Object instantiate(
@ -142,13 +182,10 @@ public class StructHelper {
return instantiator.instantiate( attributeValues, sessionFactory );
}
public static ValuedModelPart getEmbeddedPart(
EmbeddableMappingType embeddableMappingType,
int numberOfAttributes,
int position) {
return position == numberOfAttributes ?
embeddableMappingType.getDiscriminatorMapping() :
embeddableMappingType.getAttributeMapping( position );
public static ValuedModelPart getEmbeddedPart(EmbeddableMappingType embeddableMappingType, int position) {
return position == embeddableMappingType.getNumberOfAttributeMappings()
? embeddableMappingType.getDiscriminatorMapping()
: embeddableMappingType.getAttributeMapping( position );
}
private static int injectJdbcValue(
@ -158,16 +195,61 @@ public class StructHelper {
Object[] jdbcValues,
int jdbcIndex,
WrapperOptions options) throws SQLException {
final MappingType mappedType = attributeMapping.getMappedType();
final int jdbcValueCount;
if ( mappedType instanceof EmbeddableMappingType ) {
final EmbeddableMappingType embeddableMappingType = (EmbeddableMappingType) mappedType;
if ( embeddableMappingType.getAggregateMapping() != null ) {
final AggregateJdbcType aggregateJdbcType = (AggregateJdbcType) embeddableMappingType.getAggregateMapping()
.getJdbcMapping()
.getJdbcType();
if ( attributeMapping instanceof ToOneAttributeMapping ) {
final ToOneAttributeMapping toOneAttributeMapping = (ToOneAttributeMapping) attributeMapping;
if ( toOneAttributeMapping.getSideNature() == ForeignKeyDescriptor.Nature.TARGET ) {
return 0;
}
final ForeignKeyDescriptor foreignKeyDescriptor = toOneAttributeMapping.getForeignKeyDescriptor();
final ValuedModelPart keyPart = foreignKeyDescriptor.getKeyPart();
final Object foreignKeyValue = foreignKeyDescriptor.getAssociationKeyFromSide(
attributeValues[attributeIndex],
ForeignKeyDescriptor.Nature.TARGET,
options.getSession()
);
if ( keyPart instanceof BasicValuedMapping ) {
jdbcValueCount = 1;
jdbcValues[jdbcIndex] = aggregateJdbcType.createJdbcValue(
jdbcValues[jdbcIndex] = foreignKeyValue;
}
else if ( keyPart instanceof EmbeddableValuedModelPart ) {
final EmbeddableMappingType mappingType = ( (EmbeddableValuedModelPart) keyPart ).getEmbeddableTypeDescriptor();
jdbcValueCount = injectJdbcValues(
mappingType,
foreignKeyValue,
jdbcValues,
jdbcIndex,
options
);
}
else {
throw new UnsupportedOperationException( "Unsupported foreign key part: " + keyPart );
}
}
else if ( attributeMapping instanceof PluralAttributeMapping ) {
return 0;
}
else if ( attributeMapping instanceof DiscriminatedAssociationAttributeMapping ) {
jdbcValueCount = attributeMapping.decompose(
attributeValues[attributeIndex],
jdbcIndex,
jdbcValues,
options,
(valueIndex, objects, wrapperOptions, value, jdbcValueMapping) -> {
objects[valueIndex] = value;
},
options.getSession()
);
}
else if ( attributeMapping instanceof EmbeddableValuedModelPart ) {
final EmbeddableValuedModelPart embeddableValuedModelPart = (EmbeddableValuedModelPart) attributeMapping;
final EmbeddableMappingType embeddableMappingType = embeddableValuedModelPart.getMappedType();
if ( embeddableMappingType.getAggregateMapping() != null ) {
jdbcValueCount = 1;
jdbcValues[jdbcIndex] = embeddableMappingType.getAggregateMapping()
.getJdbcMapping()
.getJdbcValueBinder()
.getBindValue(
attributeValues[attributeIndex],
options
);
@ -180,7 +262,7 @@ public class StructHelper {
int offset = 0;
for ( int i = 0; i < numberOfValues; i++ ) {
offset += injectJdbcValue(
getEmbeddedPart( embeddableMappingType, numberOfAttributeMappings, i ),
getEmbeddedPart( embeddableMappingType, i ),
subValues,
i,
jdbcValues,
@ -251,23 +333,27 @@ public class StructHelper {
int[] inverseMapping,
Object[] sourceJdbcValues,
Object[] targetJdbcValues) {
final int numberOfAttributes = embeddableMappingType.getNumberOfAttributeMappings();
int targetJdbcOffset = 0;
for ( int i = 0; i < numberOfAttributes + ( embeddableMappingType.isPolymorphic() ? 1 : 0 ); i++ ) {
final ValuedModelPart attributeMapping = getEmbeddedPart( embeddableMappingType, numberOfAttributes, i );
final MappingType mappedType = attributeMapping.getMappedType();
final int jdbcValueCount = getJdbcValueCount( mappedType );
final int attributeIndex = inverseMapping[i];
int sourceJdbcIndex = 0;
for ( int j = 0; j < attributeIndex; j++ ) {
sourceJdbcIndex += getJdbcValueCount( embeddableMappingType.getAttributeMapping( j ).getMappedType() );
for ( int i = 0; i < inverseMapping.length; i++ ) {
targetJdbcValues[i] = sourceJdbcValues[inverseMapping[i]];
}
for ( int j = 0; j < jdbcValueCount; j++ ) {
targetJdbcValues[targetJdbcOffset++] = sourceJdbcValues[sourceJdbcIndex + j];
}
}
// final int numberOfAttributes = embeddableMappingType.getNumberOfAttributeMappings();
// int targetJdbcOffset = 0;
// for ( int i = 0; i < numberOfAttributes + ( embeddableMappingType.isPolymorphic() ? 1 : 0 ); i++ ) {
// final ValuedModelPart attributeMapping = getEmbeddedPart( embeddableMappingType, i );
// final MappingType mappedType = attributeMapping.getMappedType();
// final int jdbcValueCount = getJdbcValueCount( mappedType );
//
// final int attributeIndex = inverseMapping[i];
// int sourceJdbcIndex = 0;
// for ( int j = 0; j < attributeIndex; j++ ) {
// sourceJdbcIndex += getJdbcValueCount( embeddableMappingType.getAttributeMapping( j ).getMappedType() );
// }
//
// for ( int j = 0; j < jdbcValueCount; j++ ) {
// targetJdbcValues[targetJdbcOffset++] = sourceJdbcValues[sourceJdbcIndex + j];
// }
// }
}
public static int getJdbcValueCount(MappingType mappedType) {

View File

@ -13,10 +13,16 @@ import java.sql.SQLException;
import java.sql.Struct;
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.metamodel.mapping.BasicValuedMapping;
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.ValuedModelPart;
import org.hibernate.metamodel.mapping.internal.DiscriminatedAssociationAttributeMapping;
import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
import org.hibernate.type.BasicPluralType;
import org.hibernate.type.BasicType;
@ -144,9 +150,12 @@ public class StructJdbcType implements org.hibernate.type.descriptor.jdbc.Struct
@Override
public Object[] extractJdbcValues(Object rawJdbcValue, WrapperOptions options) throws SQLException {
final Object[] attributes = ( (Struct) rawJdbcValue ).getAttributes();
wrapRawJdbcValues( embeddableMappingType, orderMapping, inverseOrderMapping, attributes, 0, options );
return attributes;
final Object[] jdbcValues = ( (Struct) rawJdbcValue ).getAttributes();
if ( orderMapping != null ) {
StructHelper.orderJdbcValues( embeddableMappingType, inverseOrderMapping, jdbcValues.clone(), jdbcValues );
}
wrapRawJdbcValues( embeddableMappingType, jdbcValues, 0, options );
return jdbcValues;
}
@Override
@ -198,18 +207,21 @@ public class StructJdbcType implements org.hibernate.type.descriptor.jdbc.Struct
return null;
}
final Struct struct = (Struct) object;
final Object[] values = struct.getAttributes();
final Object[] jdbcValues = struct.getAttributes();
final boolean jdbcRepresentation = getJavaType().getJavaTypeClass() == Object[].class;
if ( jdbcRepresentation ) {
wrapRawJdbcValues( embeddableMappingType, orderMapping, inverseOrderMapping, values, 0, options );
if ( orderMapping != null ) {
StructHelper.orderJdbcValues( embeddableMappingType, inverseOrderMapping, jdbcValues.clone(), jdbcValues );
}
wrapRawJdbcValues( embeddableMappingType, jdbcValues, 0, options );
//noinspection unchecked
return (X) values;
return (X) jdbcValues;
}
assert embeddableMappingType != null && embeddableMappingType.getJavaType() == getJavaType();
final StructAttributeValues attributeValues = getAttributeValues(
embeddableMappingType,
orderMapping,
values,
jdbcValues,
options
);
//noinspection unchecked
@ -240,9 +252,8 @@ public class StructJdbcType implements org.hibernate.type.descriptor.jdbc.Struct
else {
attributeIndex = orderMapping[i];
}
final ValuedModelPart modelPart = getEmbeddedPart( embeddableMappingType, numberOfAttributeMappings, attributeIndex );
jdbcIndex += injectAttributeValue(
modelPart,
getEmbeddedPart( embeddableMappingType, attributeIndex ),
attributeValues,
attributeIndex,
rawJdbcValues,
@ -381,55 +392,96 @@ public class StructJdbcType implements org.hibernate.type.descriptor.jdbc.Struct
private int wrapRawJdbcValues(
EmbeddableMappingType embeddableMappingType,
int[] orderMapping,
int[] inverseOrderMapping,
Object[] jdbcValues,
int jdbcIndex,
WrapperOptions options) throws SQLException {
final Object[] targetJdbcValues;
if ( orderMapping == null ) {
targetJdbcValues = jdbcValues;
}
else {
targetJdbcValues = jdbcValues.clone();
}
final int numberOfAttributeMappings = embeddableMappingType.getNumberOfAttributeMappings();
for ( int i = 0; i < numberOfAttributeMappings + ( embeddableMappingType.isPolymorphic() ? 1 : 0 ); i++ ) {
final ValuedModelPart attributeMapping;
if ( orderMapping == null ) {
attributeMapping = getEmbeddedPart( embeddableMappingType, numberOfAttributeMappings, i );
final ValuedModelPart attributeMapping = getEmbeddedPart( embeddableMappingType, i );
if ( attributeMapping instanceof ToOneAttributeMapping ) {
final ToOneAttributeMapping toOneAttributeMapping = (ToOneAttributeMapping) attributeMapping;
if ( toOneAttributeMapping.getSideNature() == ForeignKeyDescriptor.Nature.TARGET ) {
continue;
}
final ForeignKeyDescriptor foreignKeyDescriptor = toOneAttributeMapping.getForeignKeyDescriptor();
final ValuedModelPart keyPart = foreignKeyDescriptor.getKeyPart();
if ( keyPart instanceof BasicValuedMapping ) {
wrapRawJdbcValue( keyPart.getSingleJdbcMapping(), jdbcValues, jdbcIndex, options );
jdbcIndex++;
}
else if ( keyPart instanceof EmbeddableValuedModelPart ) {
final EmbeddableMappingType mappingType = ( (EmbeddableValuedModelPart) keyPart ).getEmbeddableTypeDescriptor();
jdbcIndex = wrapRawJdbcValues(
mappingType,
jdbcValues,
jdbcIndex,
options
);
}
else {
attributeMapping = getEmbeddedPart( embeddableMappingType, numberOfAttributeMappings, orderMapping[i] );
throw new UnsupportedOperationException( "Unsupported foreign key part: " + keyPart );
}
final MappingType mappedType = attributeMapping.getMappedType();
if ( mappedType instanceof EmbeddableMappingType ) {
final EmbeddableMappingType embeddableType = (EmbeddableMappingType) mappedType;
}
else if ( attributeMapping instanceof PluralAttributeMapping ) {
continue;
}
else if ( attributeMapping instanceof DiscriminatedAssociationAttributeMapping ) {
final DiscriminatedAssociationAttributeMapping discriminatedAssociationAttributeMapping = (DiscriminatedAssociationAttributeMapping) attributeMapping;
wrapRawJdbcValue(
discriminatedAssociationAttributeMapping.getDiscriminatorPart().getSingleJdbcMapping(),
jdbcValues,
jdbcIndex,
options
);
jdbcIndex++;
wrapRawJdbcValue(
discriminatedAssociationAttributeMapping.getKeyPart().getSingleJdbcMapping(),
jdbcValues,
jdbcIndex,
options
);
jdbcIndex++;
}
else if ( attributeMapping instanceof EmbeddableValuedModelPart ) {
final EmbeddableValuedModelPart embeddableValuedModelPart = (EmbeddableValuedModelPart) attributeMapping;
final EmbeddableMappingType embeddableType = embeddableValuedModelPart.getMappedType();
if ( embeddableType.getAggregateMapping() != null ) {
final AggregateJdbcType aggregateJdbcType = (AggregateJdbcType) embeddableType.getAggregateMapping()
.getJdbcMapping()
.getJdbcType();
final Object rawJdbcValue = targetJdbcValues[jdbcIndex];
targetJdbcValues[jdbcIndex] = aggregateJdbcType.extractJdbcValues( rawJdbcValue, options );
final Object rawJdbcValue = jdbcValues[jdbcIndex];
jdbcValues[jdbcIndex] = aggregateJdbcType.extractJdbcValues( rawJdbcValue, options );
jdbcIndex++;
}
else {
jdbcIndex = wrapRawJdbcValues( embeddableType, null, null, targetJdbcValues, jdbcIndex, options );
jdbcIndex = wrapRawJdbcValues( embeddableType, jdbcValues, jdbcIndex, options );
}
}
else {
assert attributeMapping.getJdbcTypeCount() == 1;
final Object rawJdbcValue = targetJdbcValues[jdbcIndex];
if ( rawJdbcValue != null ) {
final JdbcMapping jdbcMapping = attributeMapping.getSingleJdbcMapping();
wrapRawJdbcValue( attributeMapping.getSingleJdbcMapping(), jdbcValues, jdbcIndex, options );
jdbcIndex++;
}
}
return jdbcIndex;
}
private void wrapRawJdbcValue(
JdbcMapping jdbcMapping,
Object[] jdbcValues,
int jdbcIndex,
WrapperOptions options) throws SQLException {
final Object rawJdbcValue = jdbcValues[jdbcIndex];
if ( rawJdbcValue == null ) {
return;
}
switch ( jdbcMapping.getJdbcType().getDefaultSqlTypeCode() ) {
case SqlTypes.TIME_WITH_TIMEZONE:
case SqlTypes.TIME_UTC:
case SqlTypes.TIMESTAMP_WITH_TIMEZONE:
case SqlTypes.TIMESTAMP_UTC:
// Only transform the raw jdbc value if it could be a TIMESTAMPTZ
targetJdbcValues[jdbcIndex] = jdbcMapping.getJdbcJavaType()
jdbcValues[jdbcIndex] = jdbcMapping.getJdbcJavaType()
.wrap( transformRawJdbcValue( rawJdbcValue, options ), options );
break;
case SqlTypes.ARRAY:
@ -451,7 +503,7 @@ public class StructJdbcType implements org.hibernate.type.descriptor.jdbc.Struct
options
);
}
targetJdbcValues[jdbcIndex] = jdbcMapping.getJdbcJavaType().wrap( newArray, options );
jdbcValues[jdbcIndex] = jdbcMapping.getJdbcJavaType().wrap( newArray, options );
break;
case SqlTypes.STRUCT:
case SqlTypes.JSON:
@ -471,26 +523,18 @@ public class StructJdbcType implements org.hibernate.type.descriptor.jdbc.Struct
);
newArray[j] = instantiate( subEmbeddableMappingType, subValues, options.getSessionFactory() );
}
targetJdbcValues[jdbcIndex] = jdbcMapping.getJdbcJavaType().wrap( newArray, options );
jdbcValues[jdbcIndex] = jdbcMapping.getJdbcJavaType().wrap( newArray, options );
break;
default:
targetJdbcValues[jdbcIndex] = jdbcMapping.getJdbcJavaType().wrap( rawJdbcValue, options );
jdbcValues[jdbcIndex] = jdbcMapping.getJdbcJavaType().wrap( rawJdbcValue, options );
break;
}
break;
default:
targetJdbcValues[jdbcIndex] = jdbcMapping.getJdbcJavaType().wrap( rawJdbcValue, options );
jdbcValues[jdbcIndex] = jdbcMapping.getJdbcJavaType().wrap( rawJdbcValue, options );
break;
}
}
jdbcIndex++;
}
}
if ( orderMapping != null ) {
StructHelper.orderJdbcValues( embeddableMappingType, inverseOrderMapping, targetJdbcValues, jdbcValues );
}
return jdbcIndex;
}
protected Object transformRawJdbcValue(Object rawJdbcValue, WrapperOptions options) {
return rawJdbcValue;

View File

@ -493,7 +493,7 @@ public class XmlHelper {
if ( array[i] == null ) {
continue;
}
final ValuedModelPart attributeMapping = getEmbeddedPart( embeddableMappingType, numberOfAttributes, i );
final ValuedModelPart attributeMapping = getEmbeddedPart( embeddableMappingType, i );
if ( attributeMapping instanceof SelectableMapping ) {
final SelectableMapping selectable = (SelectableMapping) attributeMapping;
final String tagName = selectable.getSelectableName();

View File

@ -94,7 +94,10 @@ public class GeneratedValueBasicResultBuilder implements ResultBuilder {
return new BasicResult<>(
sqlSelection.getValuesArrayPosition(),
null,
modelPart.getJdbcMapping()
modelPart.getJdbcMapping(),
navigablePath,
false,
false
);
}

View File

@ -13,6 +13,7 @@ import org.hibernate.sql.Template;
import static org.hibernate.type.SqlTypes.JSON_ARRAY;
import static org.hibernate.type.SqlTypes.STRUCT_ARRAY;
import static org.hibernate.type.SqlTypes.STRUCT_TABLE;
import static org.hibernate.type.SqlTypes.XML_ARRAY;
/**
* An aggregate column is a column of type {@link org.hibernate.type.SqlTypes#STRUCT},
@ -79,7 +80,9 @@ public class AggregateColumn extends Column {
final AggregateColumn parentAggregateColumn = component.getParentAggregateColumn();
final String simpleAggregateName = aggregateColumn.getQuotedName( dialect );
final String aggregateSelectableExpression;
if ( parentAggregateColumn == null ) {
// If the aggregate column is an array, drop the parent read expression, because this is a NestedColumnReference
// and will require special rendering
if ( parentAggregateColumn == null || isArray( aggregateColumn ) ) {
aggregateSelectableExpression = getRootAggregateSelectableExpression( aggregateColumn, simpleAggregateName );
}
else {
@ -99,13 +102,23 @@ public class AggregateColumn extends Column {
}
private static String getRootAggregateSelectableExpression(AggregateColumn aggregateColumn, String simpleAggregateName) {
if ( isArray( aggregateColumn ) ) {
return Template.TEMPLATE;
}
else {
return Template.TEMPLATE + "." + simpleAggregateName;
}
}
private static boolean isArray(AggregateColumn aggregateColumn) {
switch ( aggregateColumn.getTypeCode() ) {
case JSON_ARRAY:
case XML_ARRAY:
case STRUCT_ARRAY:
case STRUCT_TABLE:
return Template.TEMPLATE;
return true;
default:
return Template.TEMPLATE + "." + simpleAggregateName;
return false;
}
}

View File

@ -967,7 +967,7 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol
return aggregateColumn == null
? jdbcTypeCode
: getDialect().getAggregateSupport()
.aggregateComponentSqlTypeCode( aggregateColumn.getSqlTypeCode(), jdbcTypeCode );
.aggregateComponentSqlTypeCode( aggregateColumn.getSqlTypeCode( getMetadata() ), jdbcTypeCode );
}
@Override

View File

@ -24,6 +24,7 @@ import org.hibernate.Remove;
import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.boot.model.relational.Database;
import org.hibernate.boot.model.relational.ExportableProducer;
import org.hibernate.boot.model.relational.QualifiedName;
import org.hibernate.boot.model.relational.SqlStringGenerationContext;
import org.hibernate.boot.model.source.internal.hbm.MappingDocument;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
@ -93,7 +94,7 @@ public class Component extends SimpleValue implements MetaAttributable, Sortable
private AggregateColumn aggregateColumn;
private AggregateColumn parentAggregateColumn;
private String structName;
private QualifiedName structName;
private String[] structColumnNames;
private transient Class<?> componentClass;
// lazily computed based on 'properties' field: invalidate by setting to null when properties are modified
@ -307,11 +308,11 @@ public class Component extends SimpleValue implements MetaAttributable, Sortable
this.parentAggregateColumn = parentAggregateColumn;
}
public String getStructName() {
public QualifiedName getStructName() {
return structName;
}
public void setStructName(String structName) {
public void setStructName(QualifiedName structName) {
this.structName = structName;
}

View File

@ -87,6 +87,8 @@ public interface CollectionPart extends ValuedModelPart, Fetchable, JavaTypedExp
Nature getNature();
PluralAttributeMapping getCollectionAttribute();
@Override
default String getPartName() {
return getNature().getName();

View File

@ -12,9 +12,11 @@ import java.util.function.BiConsumer;
import org.hibernate.internal.util.IndexedConsumer;
import org.hibernate.internal.util.MutableInteger;
import org.hibernate.metamodel.mapping.internal.DiscriminatedAssociationAttributeMapping;
import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping;
import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess;
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
import org.hibernate.metamodel.spi.EmbeddableRepresentationStrategy;
import org.hibernate.property.access.spi.Getter;
import org.hibernate.spi.NavigablePath;
@ -156,9 +158,43 @@ public interface EmbeddableMappingType extends ManagedMappingType, SelectableMap
int count = 0;
for ( int i = 0; i < numberOfAttributeMappings; i++ ) {
final AttributeMapping attributeMapping = getAttributeMapping( i );
final MappingType mappedType = attributeMapping.getMappedType();
if ( mappedType instanceof EmbeddableMappingType ) {
final EmbeddableMappingType embeddableMappingType = (EmbeddableMappingType) mappedType;
if ( attributeMapping instanceof DiscriminatedAssociationAttributeMapping ) {
final DiscriminatedAssociationAttributeMapping discriminatedAssociationAttributeMapping = (DiscriminatedAssociationAttributeMapping) attributeMapping;
if ( count == columnIndex ) {
return discriminatedAssociationAttributeMapping.getDiscriminatorMapping();
}
count++;
if ( count == columnIndex ) {
return discriminatedAssociationAttributeMapping.getKeyPart();
}
count++;
}
else if ( attributeMapping instanceof ToOneAttributeMapping ) {
final ToOneAttributeMapping toOneAttributeMapping = (ToOneAttributeMapping) attributeMapping;
if ( toOneAttributeMapping.getSideNature() == ForeignKeyDescriptor.Nature.KEY ) {
final ValuedModelPart keyPart = toOneAttributeMapping.getForeignKeyDescriptor().getKeyPart();
if ( keyPart instanceof BasicValuedMapping ) {
if ( count == columnIndex ) {
return (SelectableMapping) keyPart;
}
count++;
}
else if ( keyPart instanceof EmbeddableValuedModelPart ) {
final EmbeddableMappingType mappingType = ( (EmbeddableValuedModelPart) keyPart ).getEmbeddableTypeDescriptor();
final SelectableMapping selectable = mappingType.getJdbcValueSelectable( columnIndex - count );
if ( selectable != null ) {
return selectable;
}
count += mappingType.getJdbcValueCount();
}
else {
throw new UnsupportedOperationException( "Unsupported foreign key part: " + keyPart );
}
}
}
else if ( attributeMapping instanceof EmbeddableValuedModelPart ) {
final EmbeddableValuedModelPart embeddableValuedModelPart = (EmbeddableValuedModelPart) attributeMapping;
final EmbeddableMappingType embeddableMappingType = embeddableValuedModelPart.getMappedType();
final SelectableMapping aggregateMapping = embeddableMappingType.getAggregateMapping();
if ( aggregateMapping == null ) {
final SelectableMapping subSelectable = embeddableMappingType.getJdbcValueSelectable( columnIndex - count );
@ -176,8 +212,11 @@ public interface EmbeddableMappingType extends ManagedMappingType, SelectableMap
}
else {
if ( count == columnIndex ) {
if ( attributeMapping instanceof SelectableMapping ) {
return (SelectableMapping) attributeMapping;
}
assert attributeMapping.getJdbcTypeCount() == 0;
}
count += attributeMapping.getJdbcTypeCount();
}
}

View File

@ -138,7 +138,7 @@ public interface PluralAttributeMapping
TableGroup parentTableGroup,
String resultVariable,
DomainResultCreationState creationState) {
return new BasicResult( 0, null, getJavaType() );
return new BasicResult( 0, null, getJavaType(), null, null, false, false );
}
String getSeparateCollectionTable();

View File

@ -13,7 +13,6 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.util.IndexedConsumer;
import org.hibernate.metamodel.mapping.DiscriminatorConverter;
import org.hibernate.metamodel.mapping.DiscriminatorType;
import org.hibernate.metamodel.mapping.DiscriminatorValueDetails;
import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.JdbcMapping;
@ -124,7 +123,9 @@ public abstract class AbstractDiscriminatorMapping implements EntityDiscriminato
resultVariable,
discriminatorType.getJavaTypeDescriptor(),
discriminatorType.getValueConverter(),
navigablePath
navigablePath,
false,
!sqlSelection.isVirtual()
);
}
@ -175,7 +176,10 @@ public abstract class AbstractDiscriminatorMapping implements EntityDiscriminato
this,
discriminatorType.getValueConverter(),
fetchTiming,
creationState
true,
creationState,
false,
!sqlSelection.isVirtual()
);
}

View File

@ -120,6 +120,11 @@ public abstract class AbstractEntityCollectionPart implements EntityCollectionPa
return nature;
}
@Override
public PluralAttributeMapping getCollectionAttribute() {
return collectionDescriptor.getAttributeMapping();
}
@Override
public String getFetchableName() {
return nature.getName();

View File

@ -37,7 +37,6 @@ import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.sql.results.graph.Fetch;
import org.hibernate.sql.results.graph.FetchOptions;
import org.hibernate.sql.results.graph.FetchParent;
import org.hibernate.sql.results.graph.basic.BasicFetch;
@ -59,6 +58,8 @@ public class AnyDiscriminatorPart implements DiscriminatorMapping, FetchOptions
private final String table;
private final String column;
private final String customReadExpression;
private final String customWriteExpression;
private final String columnDefinition;
private final Long length;
private final Integer precision;
@ -75,7 +76,7 @@ public class AnyDiscriminatorPart implements DiscriminatorMapping, FetchOptions
NavigableRole partRole,
DiscriminatedAssociationModelPart declaringType,
String table,
String column,
String column, String customReadExpression, String customWriteExpression,
String columnDefinition,
Long length,
Integer precision,
@ -90,6 +91,8 @@ public class AnyDiscriminatorPart implements DiscriminatorMapping, FetchOptions
this.declaringType = declaringType;
this.table = table;
this.column = column;
this.customReadExpression = customReadExpression;
this.customWriteExpression = customWriteExpression;
this.columnDefinition = columnDefinition;
this.length = length;
this.precision = precision;
@ -160,12 +163,12 @@ public class AnyDiscriminatorPart implements DiscriminatorMapping, FetchOptions
@Override
public String getCustomReadExpression() {
return null;
return customReadExpression;
}
@Override
public String getCustomWriteExpression() {
return null;
return customWriteExpression;
}
@Override
@ -326,7 +329,8 @@ public class AnyDiscriminatorPart implements DiscriminatorMapping, FetchOptions
fetchablePath,
this,
fetchTiming,
creationState
creationState,
!sqlSelection.isVirtual()
);
}
@ -351,7 +355,9 @@ public class AnyDiscriminatorPart implements DiscriminatorMapping, FetchOptions
sqlSelection.getValuesArrayPosition(),
resultVariable,
jdbcMapping(),
navigablePath
navigablePath,
false,
!sqlSelection.isVirtual()
);
}

View File

@ -48,6 +48,8 @@ public class AnyKeyPart implements BasicValuedModelPart, FetchOptions {
private final String table;
private final String column;
private final DiscriminatedAssociationModelPart anyPart;
private final String customReadExpression;
private final String customWriteExpression;
private final String columnDefinition;
private final Long length;
private final Integer precision;
@ -63,6 +65,8 @@ public class AnyKeyPart implements BasicValuedModelPart, FetchOptions {
DiscriminatedAssociationModelPart anyPart,
String table,
String column,
String customReadExpression,
String customWriteExpression,
String columnDefinition,
Long length,
Integer precision,
@ -76,6 +80,8 @@ public class AnyKeyPart implements BasicValuedModelPart, FetchOptions {
this.table = table;
this.column = column;
this.anyPart = anyPart;
this.customReadExpression = customReadExpression;
this.customWriteExpression = customWriteExpression;
this.columnDefinition = columnDefinition;
this.length = length;
this.precision = precision;
@ -124,12 +130,12 @@ public class AnyKeyPart implements BasicValuedModelPart, FetchOptions {
@Override
public String getCustomReadExpression() {
return null;
return customReadExpression;
}
@Override
public String getCustomWriteExpression() {
return null;
return customWriteExpression;
}
@Override
@ -242,7 +248,8 @@ public class AnyKeyPart implements BasicValuedModelPart, FetchOptions {
fetchablePath,
this,
fetchTiming,
creationState
creationState,
!sqlSelection.isVirtual()
);
}
@ -333,7 +340,9 @@ public class AnyKeyPart implements BasicValuedModelPart, FetchOptions {
sqlSelection.getValuesArrayPosition(),
resultVariable,
jdbcMapping,
navigablePath
navigablePath,
false,
!sqlSelection.isVirtual()
);
}

View File

@ -337,7 +337,9 @@ public class BasicAttributeMapping
sqlSelection.getValuesArrayPosition(),
resultVariable,
jdbcMapping,
navigablePath
navigablePath,
false,
!sqlSelection.isVirtual()
);
}
@ -391,10 +393,12 @@ public class BasicAttributeMapping
DomainResultCreationState creationState) {
final int valuesArrayPosition;
boolean coerceResultType = false;
final SqlSelection sqlSelection;
if ( fetchTiming == FetchTiming.DELAYED && isLazy ) {
// Lazy property. A valuesArrayPosition of -1 will lead to
// returning a domain result assembler that returns LazyPropertyInitializer.UNFETCHED_PROPERTY
valuesArrayPosition = -1;
sqlSelection = null;
}
else {
final SqlAstCreationState sqlAstCreationState = creationState.getSqlAstCreationState();
@ -404,7 +408,7 @@ public class BasicAttributeMapping
assert tableGroup != null;
final SqlSelection sqlSelection = resolveSqlSelection(
sqlSelection = resolveSqlSelection(
fetchablePath,
tableGroup,
fetchParent,
@ -422,9 +426,12 @@ public class BasicAttributeMapping
fetchParent,
fetchablePath,
this,
getJdbcMapping().getValueConverter(),
fetchTiming,
true,
creationState,
coerceResultType
coerceResultType,
sqlSelection != null && !sqlSelection.isVirtual()
);
}

View File

@ -231,7 +231,9 @@ public class BasicEntityIdentifierMappingImpl implements BasicEntityIdentifierMa
sqlSelection.getValuesArrayPosition(),
resultVariable,
entityPersister.getIdentifierMapping().getSingleJdbcMapping(),
navigablePath
navigablePath,
false,
!sqlSelection.isVirtual()
);
}
@ -425,10 +427,13 @@ public class BasicEntityIdentifierMappingImpl implements BasicEntityIdentifierMa
fetchParent,
fetchablePath,
this,
getJdbcMapping().getValueConverter(),
FetchTiming.IMMEDIATE,
true,
creationState,
// if the expression type is different that the expected type coerce the value
selectionType != null && selectionType.getSingleJdbcMapping().getJdbcJavaType() != getJdbcMapping().getJdbcJavaType()
selectionType != null && selectionType.getSingleJdbcMapping().getJdbcJavaType() != getJdbcMapping().getJdbcJavaType(),
!sqlSelection.isVirtual()
);
}

View File

@ -17,6 +17,7 @@ import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.SelectableConsumer;
import org.hibernate.metamodel.mapping.SelectableMapping;
import org.hibernate.metamodel.model.domain.NavigableRole;
@ -66,6 +67,11 @@ public class BasicValuedCollectionPart
return nature;
}
@Override
public PluralAttributeMapping getCollectionAttribute() {
return collectionDescriptor.getAttributeMapping();
}
@Override
public MappingType getPartMappingType() {
return selectableMapping.getJdbcMapping()::getJavaTypeDescriptor;
@ -168,7 +174,9 @@ public class BasicValuedCollectionPart
sqlSelection.getValuesArrayPosition(),
resultVariable,
selectableMapping.getJdbcMapping(),
navigablePath
navigablePath,
false,
!sqlSelection.isVirtual()
);
}
@ -280,7 +288,8 @@ public class BasicValuedCollectionPart
fetchablePath,
this,
FetchTiming.IMMEDIATE,
creationState
creationState,
!sqlSelection.isVirtual()
);
}

View File

@ -18,6 +18,7 @@ import org.hibernate.metamodel.mapping.CollectionIdentifierDescriptor;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.SelectableConsumer;
import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.persister.collection.CollectionPersister;
@ -65,6 +66,11 @@ public class CollectionIdentifierDescriptorImpl implements CollectionIdentifierD
return Nature.ID;
}
@Override
public PluralAttributeMapping getCollectionAttribute() {
return collectionDescriptor.getAttributeMapping();
}
@Override
public String getContainingTableExpression() {
return containingTableName;
@ -278,7 +284,8 @@ public class CollectionIdentifierDescriptorImpl implements CollectionIdentifierD
fetchablePath,
this,
FetchTiming.IMMEDIATE,
creationState
creationState,
!sqlSelection.isVirtual()
);
}
@ -305,7 +312,9 @@ public class CollectionIdentifierDescriptorImpl implements CollectionIdentifierD
sqlSelection.getValuesArrayPosition(),
null,
type,
collectionPath
collectionPath,
false,
!sqlSelection.isVirtual()
);
}

View File

@ -80,6 +80,8 @@ public class DiscriminatedAssociationMapping implements MappingType, FetchOption
declaringModelPart,
tableName,
metaColumn.getText( dialect ),
metaColumn.getCustomReadExpression(),
metaColumn.getCustomWriteExpression(),
metaColumn.getSqlType(),
metaColumn.getLength(),
metaColumn.getPrecision(),
@ -99,6 +101,8 @@ public class DiscriminatedAssociationMapping implements MappingType, FetchOption
declaringModelPart,
tableName,
keyColumn.getText( dialect ),
keyColumn.getCustomReadExpression(),
keyColumn.getCustomWriteExpression(),
keyColumn.getSqlType(),
keyColumn.getLength(),
keyColumn.getPrecision(),

View File

@ -22,6 +22,7 @@ import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.SelectableConsumer;
import org.hibernate.metamodel.mapping.SelectableMapping;
import org.hibernate.metamodel.model.domain.NavigableRole;
@ -80,6 +81,11 @@ public class DiscriminatedCollectionPart implements DiscriminatedAssociationMode
return nature;
}
@Override
public PluralAttributeMapping getCollectionAttribute() {
return collectionDescriptor.getAttributeMapping();
}
@Override
public DiscriminatorMapping getDiscriminatorMapping() {
return associationMapping.getDiscriminatorPart();

View File

@ -292,7 +292,7 @@ public class EmbeddableMappingTypeImpl extends AbstractEmbeddableMapping impleme
case STRUCT_TABLE:
isArray = true;
aggregateSqlTypeCode = STRUCT;
structTypeName = bootDescriptor.getStructName();
structTypeName = bootDescriptor.getStructName().render();
if ( structTypeName == null ) {
final String arrayTypeName = aggregateColumn.getSqlType( creationContext.getMetadata() );
if ( arrayTypeName.endsWith( " array" ) ) {
@ -322,7 +322,7 @@ public class EmbeddableMappingTypeImpl extends AbstractEmbeddableMapping impleme
);
// Register the resolved type under its struct name and java class name
if ( bootDescriptor.getStructName() != null ) {
basicTypeRegistry.register( basicType, bootDescriptor.getStructName() );
basicTypeRegistry.register( basicType, bootDescriptor.getStructName().render() );
basicTypeRegistry.register( basicType, getMappedJavaType().getJavaTypeClass().getName() );
}
final BasicValue basicValue = (BasicValue) aggregateColumn.getValue();

View File

@ -94,6 +94,11 @@ public class EmbeddedCollectionPart implements CollectionPart, EmbeddableValuedF
this.sqlAliasStem = sqlAliasStem;
}
@Override
public PluralAttributeMapping getCollectionAttribute() {
return collectionDescriptor.getAttributeMapping();
}
@Override
public <T> DomainResult<T> createDomainResult(
NavigablePath navigablePath,

View File

@ -108,7 +108,9 @@ public class EntityRowIdMappingImpl implements EntityRowIdMapping {
sqlSelection.getValuesArrayPosition(),
resultVariable,
rowIdType,
navigablePath
navigablePath,
false,
!sqlSelection.isVirtual()
);
}

View File

@ -263,7 +263,8 @@ public class EntityVersionMappingImpl implements EntityVersionMapping, FetchOpti
fetchablePath,
this,
fetchTiming,
creationState
creationState,
!sqlSelection.isVirtual()
);
}
@ -279,7 +280,9 @@ public class EntityVersionMappingImpl implements EntityVersionMapping, FetchOpti
sqlSelection.getValuesArrayPosition(),
resultVariable,
versionBasicType,
navigablePath
navigablePath,
false,
!sqlSelection.isVirtual()
);
}

View File

@ -384,8 +384,10 @@ public class SimpleForeignKeyDescriptor implements ForeignKeyDescriptor, BasicVa
sqlSelection.getValuesArrayPosition(),
null,
selectableMapping.getJdbcMapping(),
navigablePath,
// if the expression type is different that the expected type coerce the value
selectionType != null && selectionType.getSingleJdbcMapping().getJdbcJavaType() != javaType
selectionType != null && selectionType.getSingleJdbcMapping().getJdbcJavaType() != javaType,
!sqlSelection.isVirtual()
);
}

View File

@ -163,7 +163,14 @@ public class SoftDeleteMappingImpl implements SoftDeleteMapping {
String resultVariable,
DomainResultCreationState creationState) {
final SqlSelection sqlSelection = resolveSqlSelection( navigablePath, tableGroup, creationState );
return new BasicResult<>( sqlSelection.getValuesArrayPosition(), resultVariable, getJdbcMapping() );
return new BasicResult<>(
sqlSelection.getValuesArrayPosition(),
resultVariable,
getJdbcMapping(),
navigablePath,
false,
!sqlSelection.isVirtual()
);
}
private SqlSelection resolveSqlSelection(

View File

@ -18,6 +18,7 @@ import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.sql.results.graph.basic.BasicResult;
import org.hibernate.sql.results.graph.basic.BasicResultAssembler;
import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata;
import org.hibernate.type.BasicType;
import org.hibernate.type.descriptor.java.JavaType;
@ -69,7 +70,10 @@ public class ScalarDomainResultBuilder<T> implements ResultBuilder {
return new BasicResult<>(
sqlSelection.getValuesArrayPosition(),
null,
( (BasicType<?>) sqlSelection.getExpressionType() )
(BasicType<?>) sqlSelection.getExpressionType(),
null,
false,
false
);
}

View File

@ -218,7 +218,9 @@ public class AnonymousTupleBasicValuedModelPart implements OwnedValuedModelPart,
sqlSelection.getValuesArrayPosition(),
resultVariable,
jdbcMapping,
navigablePath
navigablePath,
false,
!sqlSelection.isVirtual()
);
}
@ -270,7 +272,8 @@ public class AnonymousTupleBasicValuedModelPart implements OwnedValuedModelPart,
fetchablePath,
this,
fetchTiming,
creationState
creationState,
!sqlSelection.isVirtual()
);
}

View File

@ -31,6 +31,7 @@ import org.hibernate.query.results.dynamic.DynamicFetchBuilderLegacy;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.graph.basic.BasicResult;
import org.hibernate.sql.results.graph.basic.BasicResultAssembler;
import org.hibernate.sql.results.graph.entity.EntityResult;
import org.hibernate.sql.results.jdbc.spi.JdbcValuesMapping;
import org.hibernate.sql.results.jdbc.spi.JdbcValuesMappingProducer;
@ -328,7 +329,10 @@ public class ResultSetMappingImpl implements ResultSetMapping {
return new BasicResult(
valuesArrayPosition,
name,
jdbcMapping
jdbcMapping,
null,
false,
false
);
}

View File

@ -13,9 +13,7 @@ import org.hibernate.query.results.ResultsHelper;
import org.hibernate.spi.NavigablePath;
import org.hibernate.query.results.DomainResultCreationStateImpl;
import org.hibernate.query.results.ResultBuilder;
import org.hibernate.query.results.ResultSetMappingSqlSelection;
import org.hibernate.query.results.dynamic.DynamicFetchBuilderLegacy;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableReference;
@ -24,7 +22,6 @@ import org.hibernate.sql.results.graph.basic.BasicResult;
import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata;
import static org.hibernate.query.results.ResultsHelper.impl;
import static org.hibernate.query.results.ResultsHelper.jdbcPositionToValuesArrayPosition;
/**
* CompleteResultBuilder for basic-valued ModelParts
@ -93,7 +90,10 @@ public class CompleteResultBuilderBasicModelPart
return new BasicResult<>(
sqlSelection.getValuesArrayPosition(),
columnAlias,
modelPart.getJdbcMapping()
modelPart.getJdbcMapping(),
navigablePath,
false,
!sqlSelection.isVirtual()
);
}

View File

@ -111,7 +111,10 @@ public class CompleteResultBuilderBasicValuedConverted<O,R> implements CompleteR
sqlSelection.getValuesArrayPosition(),
columnName,
valueConverter.getDomainJavaType(),
valueConverter
valueConverter,
null,
false,
false
);
}

View File

@ -112,7 +112,10 @@ public class CompleteResultBuilderBasicValuedStandard implements CompleteResultB
return new BasicResult<>(
sqlSelection.getValuesArrayPosition(),
columnName,
jdbcMapping
jdbcMapping,
null,
false,
false
);
}

View File

@ -19,6 +19,7 @@ import org.hibernate.query.results.dynamic.DynamicFetchBuilderLegacy;
import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.sql.results.graph.FetchParent;
import org.hibernate.sql.results.graph.basic.BasicFetch;
import org.hibernate.sql.results.graph.basic.BasicResultAssembler;
import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata;
/**
@ -69,7 +70,9 @@ public class DelayedFetchBuilderBasicPart
null,
FetchTiming.DELAYED,
isEnhancedForLazyLoading,
domainResultCreationState
domainResultCreationState,
false,
false
);
}

View File

@ -14,7 +14,6 @@ import org.hibernate.metamodel.mapping.internal.BasicAttributeMapping;
import org.hibernate.query.NativeQuery;
import org.hibernate.query.results.DomainResultCreationStateImpl;
import org.hibernate.query.results.ResultSetMappingSqlSelection;
import org.hibernate.query.results.ResultsHelper;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.results.graph.DomainResult;
@ -103,7 +102,10 @@ public class DynamicResultBuilderAttribute implements DynamicResultBuilder, Nati
return new BasicResult<>(
sqlSelection.getValuesArrayPosition(),
columnAlias,
attributeMapping.getJdbcMapping()
attributeMapping.getJdbcMapping(),
null,
false,
!sqlSelection.isVirtual()
);
}

View File

@ -141,7 +141,10 @@ public class DynamicResultBuilderBasicConverted<O,R> implements DynamicResultBui
sqlSelection.getValuesArrayPosition(),
columnAlias,
basicValueConverter.getDomainJavaType(),
basicValueConverter
basicValueConverter,
null,
false,
false
);
}

View File

@ -180,7 +180,15 @@ public class DynamicResultBuilderBasicStandard implements DynamicResultBuilderBa
// StandardRowReader expects there to be a JavaType as part of the ResultAssembler.
assert javaType != null;
return new BasicResult( sqlSelection.getValuesArrayPosition(), resultAlias, javaType, converter );
return new BasicResult(
sqlSelection.getValuesArrayPosition(),
resultAlias,
javaType,
converter,
null,
false,
false
);
}
@Override

View File

@ -17,7 +17,6 @@ import org.hibernate.query.results.BasicValuedFetchBuilder;
import org.hibernate.query.results.DomainResultCreationStateImpl;
import org.hibernate.query.results.FetchBuilder;
import org.hibernate.query.results.ResultsHelper;
import org.hibernate.query.results.ResultSetMappingSqlSelection;
import org.hibernate.query.results.dynamic.DynamicFetchBuilderLegacy;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.expression.Expression;
@ -28,7 +27,6 @@ import org.hibernate.sql.results.graph.basic.BasicFetch;
import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata;
import static org.hibernate.query.results.ResultsHelper.impl;
import static org.hibernate.query.results.ResultsHelper.jdbcPositionToValuesArrayPosition;
import static org.hibernate.sql.ast.spi.SqlExpressionResolver.createColumnReferenceKey;
/**
@ -118,7 +116,8 @@ public class ImplicitFetchBuilderBasic implements ImplicitFetchBuilder, BasicVal
fetchPath,
fetchable,
FetchTiming.IMMEDIATE,
domainResultCreationState
domainResultCreationState,
!sqlSelection.isVirtual()
);
}

View File

@ -174,7 +174,10 @@ public class SelfRenderingFunctionSqlAstExpression
.getValuesArrayPosition(),
resultVariable,
type == null ? null : type.getExpressibleJavaType(),
converter
converter,
null,
false,
false
);
}

View File

@ -15,6 +15,7 @@ import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.sql.results.graph.basic.BasicResult;
import org.hibernate.sql.results.graph.basic.BasicResultAssembler;
/**
* A wrapper around a basic {@link Expression} that produces a {@link BasicResult} as domain result.
@ -33,7 +34,9 @@ public class ExpressionDomainResultProducer implements DomainResultProducer<Obje
sqlSelection.getValuesArrayPosition(),
resultVariable,
expression.getExpressionType().getSingleJdbcMapping(),
null
null,
false,
false
);
}

View File

@ -78,6 +78,7 @@ import org.hibernate.sql.exec.spi.JdbcOperationQueryMutation;
import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect;
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
import org.hibernate.sql.results.graph.basic.BasicFetch;
import org.hibernate.sql.results.graph.basic.BasicResultAssembler;
import org.hibernate.sql.results.internal.SqlSelectionImpl;
import org.hibernate.sql.results.spi.ListResultsConsumer;
import org.hibernate.type.descriptor.ValueBinder;
@ -402,7 +403,8 @@ public class InsertExecutionDelegate implements TableBasedInsertHandler.Executio
null,
identifierMapping,
FetchTiming.IMMEDIATE,
null
null,
false
)
)
);

View File

@ -105,14 +105,17 @@ public class SqlAstQueryPartProcessingStateImpl
else {
throw new IllegalArgumentException( "Illegal expression passed for nested fetching: " + expression );
}
final int selectableIndex = nestingFetchParent.getReferencedMappingType().getSelectableIndex( selectableName );
if ( selectableIndex != -1 ) {
return expression.createSqlSelection(
-1,
nestingFetchParent.getReferencedMappingType().getSelectableIndex( selectableName ),
selectableIndex,
javaType,
true,
typeConfiguration
);
}
}
final Map<Expression, Object> selectionMap;
if ( deduplicateSelectionItems ) {
if ( sqlSelectionMap == null ) {

View File

@ -108,7 +108,10 @@ public class SqmParameterInterpretation implements Expression, DomainResultProdu
sqlSelection.getValuesArrayPosition(),
resultVariable,
jdbcMapping.getMappedJavaType(),
converter
converter,
null,
false,
false
);
}

View File

@ -38,26 +38,6 @@ public class BasicFetch<T> implements Fetch, BasicResultGraphNode<T> {
private final FetchTiming fetchTiming;
public BasicFetch(
int valuesArrayPosition,
FetchParent fetchParent,
NavigablePath fetchablePath,
BasicValuedModelPart valuedMapping,
FetchTiming fetchTiming,
DomainResultCreationState creationState) {
//noinspection unchecked
this(
valuesArrayPosition,
fetchParent,
fetchablePath,
valuedMapping,
(BasicValueConverter<T, ?>) valuedMapping.getJdbcMapping().getValueConverter(),
fetchTiming,
true,
creationState
);
}
public BasicFetch(
int valuesArrayPosition,
FetchParent fetchParent,
@ -65,7 +45,7 @@ public class BasicFetch<T> implements Fetch, BasicResultGraphNode<T> {
BasicValuedModelPart valuedMapping,
FetchTiming fetchTiming,
DomainResultCreationState creationState,
boolean coerceResultType) {
boolean unwrapRowProcessingState) {
//noinspection unchecked
this(
valuesArrayPosition,
@ -76,49 +56,8 @@ public class BasicFetch<T> implements Fetch, BasicResultGraphNode<T> {
fetchTiming,
true,
creationState,
coerceResultType
);
}
public BasicFetch(
int valuesArrayPosition,
FetchParent fetchParent,
NavigablePath fetchablePath,
BasicValuedModelPart valuedMapping,
BasicValueConverter<T, ?> valueConverter,
FetchTiming fetchTiming,
DomainResultCreationState creationState) {
this(
valuesArrayPosition,
fetchParent,
fetchablePath,
valuedMapping,
valueConverter,
fetchTiming,
true,
creationState
);
}
public BasicFetch(
int valuesArrayPosition,
FetchParent fetchParent,
NavigablePath fetchablePath,
BasicValuedModelPart valuedMapping,
BasicValueConverter<T, ?> valueConverter,
FetchTiming fetchTiming,
boolean canBasicPartFetchBeDelayed,
DomainResultCreationState creationState) {
this(
valuesArrayPosition,
fetchParent,
fetchablePath,
valuedMapping,
valueConverter,
fetchTiming,
canBasicPartFetchBeDelayed,
creationState,
false
false,
unwrapRowProcessingState
);
}
@ -131,7 +70,8 @@ public class BasicFetch<T> implements Fetch, BasicResultGraphNode<T> {
FetchTiming fetchTiming,
boolean canBasicPartFetchBeDelayed,
DomainResultCreationState creationState,
boolean coerceResultType) {
boolean coerceResultType,
boolean unwrapRowProcessingState) {
this.navigablePath = fetchablePath;
this.fetchParent = fetchParent;
@ -149,10 +89,10 @@ public class BasicFetch<T> implements Fetch, BasicResultGraphNode<T> {
}
else {
if (coerceResultType) {
this.assembler = new CoercingResultAssembler<>( valuesArrayPosition, javaType, valueConverter );
this.assembler = new CoercingResultAssembler<>( valuesArrayPosition, javaType, valueConverter, unwrapRowProcessingState );
}
else {
this.assembler = new BasicResultAssembler<>( valuesArrayPosition, javaType, valueConverter );
this.assembler = new BasicResultAssembler<>( valuesArrayPosition, javaType, valueConverter, unwrapRowProcessingState );
}
}
}

View File

@ -40,7 +40,9 @@ public class BasicResult<T> implements DomainResult<T>, BasicResultGraphNode<T>
jdbcValuesArrayPosition,
resultVariable,
jdbcMapping,
null
null,
false,
false
);
}
@ -48,81 +50,38 @@ public class BasicResult<T> implements DomainResult<T>, BasicResultGraphNode<T>
int jdbcValuesArrayPosition,
String resultVariable,
JdbcMapping jdbcMapping,
boolean coerceResultType) {
NavigablePath navigablePath,
boolean coerceResultType,
boolean unwrapRowProcessingState) {
//noinspection unchecked
this(
jdbcValuesArrayPosition,
resultVariable,
jdbcMapping.getJavaTypeDescriptor(),
jdbcMapping.getValueConverter(),
null,
coerceResultType
navigablePath,
coerceResultType,
unwrapRowProcessingState
);
}
public BasicResult(
int jdbcValuesArrayPosition,
String resultVariable,
JdbcMapping jdbcMapping,
NavigablePath navigablePath) {
//noinspection unchecked
this(
jdbcValuesArrayPosition,
resultVariable,
(JavaType<T>) jdbcMapping.getJavaTypeDescriptor(),
(BasicValueConverter<T, ?>) jdbcMapping.getValueConverter(),
navigablePath
);
}
public BasicResult(
int jdbcValuesArrayPosition,
String resultVariable,
JavaType<T> javaType) {
this( jdbcValuesArrayPosition, resultVariable, javaType, null, null );
}
public BasicResult(
int jdbcValuesArrayPosition,
String resultVariable,
JavaType<T> javaType,
NavigablePath navigablePath) {
this( jdbcValuesArrayPosition, resultVariable, javaType, null, navigablePath );
}
public BasicResult(
int valuesArrayPosition,
String resultVariable,
JavaType<T> javaType,
BasicValueConverter<T,?> valueConverter) {
this( valuesArrayPosition, resultVariable, javaType, valueConverter, null );
}
public BasicResult(
int valuesArrayPosition,
String resultVariable,
JavaType<T> javaType,
BasicValueConverter<T,?> valueConverter,
NavigablePath navigablePath) {
this( valuesArrayPosition, resultVariable, javaType, valueConverter, navigablePath, false );
}
public BasicResult(
int valuesArrayPosition,
String resultVariable,
JavaType<T> javaType,
BasicValueConverter<T,?> valueConverter,
NavigablePath navigablePath,
boolean coerceResultType) {
boolean coerceResultType,
boolean unwrapRowProcessingState) {
this.resultVariable = resultVariable;
this.javaType = javaType;
this.navigablePath = navigablePath;
if ( coerceResultType ) {
this.assembler = new CoercingResultAssembler<>( valuesArrayPosition, javaType, valueConverter );
this.assembler = new CoercingResultAssembler<>( valuesArrayPosition, javaType, valueConverter, unwrapRowProcessingState );
}
else {
this.assembler = new BasicResultAssembler<>( valuesArrayPosition, javaType, valueConverter );
this.assembler = new BasicResultAssembler<>( valuesArrayPosition, javaType, valueConverter, unwrapRowProcessingState );
}
}

View File

@ -29,26 +29,30 @@ public class BasicResultAssembler<J> implements DomainResultAssembler<J> {
protected final int valuesArrayPosition;
protected final JavaType<J> assembledJavaType;
private final BasicValueConverter<J,?> valueConverter;
private final boolean unwrapRowProcessingState;
public BasicResultAssembler(
int valuesArrayPosition,
JavaType<J> assembledJavaType) {
this( valuesArrayPosition, assembledJavaType, null );
public BasicResultAssembler(int valuesArrayPosition, JavaType<J> assembledJavaType) {
this( valuesArrayPosition, assembledJavaType, null, false );
}
public BasicResultAssembler(
int valuesArrayPosition,
JavaType<J> assembledJavaType,
BasicValueConverter<J, ?> valueConverter) {
BasicValueConverter<J, ?> valueConverter,
boolean unwrapRowProcessingState) {
this.valuesArrayPosition = valuesArrayPosition;
this.assembledJavaType = assembledJavaType;
this.valueConverter = valueConverter;
this.unwrapRowProcessingState = unwrapRowProcessingState;
}
/**
* Access to the raw value (unconverted, if a converter applied)
*/
public Object extractRawValue(RowProcessingState rowProcessingState) {
if ( unwrapRowProcessingState ) {
rowProcessingState = rowProcessingState.unwrap();
}
return rowProcessingState.getJdbcValue( valuesArrayPosition );
}

View File

@ -21,8 +21,9 @@ public class CoercingResultAssembler<J> extends BasicResultAssembler<J> {
public CoercingResultAssembler(
int valuesArrayPosition,
JavaType<J> assembledJavaType,
BasicValueConverter<J, ?> valueConverter) {
super( valuesArrayPosition, assembledJavaType, valueConverter );
BasicValueConverter<J, ?> valueConverter,
boolean nestedInAggregateComponent) {
super( valuesArrayPosition, assembledJavaType, valueConverter, nestedInAggregateComponent );
}
/**
@ -31,7 +32,7 @@ public class CoercingResultAssembler<J> extends BasicResultAssembler<J> {
@Override
public Object extractRawValue(RowProcessingState rowProcessingState) {
return assembledJavaType.coerce(
rowProcessingState.getJdbcValue( valuesArrayPosition ),
super.extractRawValue( rowProcessingState ),
rowProcessingState.getSession()
);
}

View File

@ -74,7 +74,8 @@ public class EmbeddableExpressionResultImpl<T> extends AbstractFetchParent imple
resolveNavigablePath( attribute ),
attribute,
FetchTiming.IMMEDIATE,
creationState
creationState,
!sqlSelection.isVirtual()
)
);
}

View File

@ -49,6 +49,11 @@ public class NestedRowProcessingState extends BaseExecutionContext implements Ro
return jdbcValue == null ? null : jdbcValue[position];
}
@Override
public RowProcessingState unwrap() {
return processingState;
}
// -- delegate the rest
@Override

View File

@ -68,4 +68,12 @@ public interface RowProcessingState extends ExecutionContext {
finishRowProcessing();
}
/**
* If this is a row processing state for aggregate components,
* this will return the underlying row processing state.
*/
default RowProcessingState unwrap() {
return this;
}
}

View File

@ -76,7 +76,7 @@ public class BooleanJavaType extends AbstractClassJavaType<Boolean> implements
if ( value == null ) {
return null;
}
if ( Boolean.class.isAssignableFrom( type ) ) {
if ( Boolean.class.isAssignableFrom( type ) || type == Object.class ) {
return (X) value;
}
if ( Byte.class.isAssignableFrom( type ) ) {

View File

@ -47,7 +47,7 @@ public class ByteJavaType extends AbstractClassJavaType<Byte>
if ( value == null ) {
return null;
}
if ( Byte.class.isAssignableFrom( type ) ) {
if ( Byte.class.isAssignableFrom( type ) || type == Object.class ) {
return (X) value;
}
if ( Short.class.isAssignableFrom( type ) ) {

View File

@ -44,7 +44,7 @@ public class CharacterJavaType extends AbstractClassJavaType<Character> implemen
if ( value == null ) {
return null;
}
if ( Character.class.isAssignableFrom( type ) ) {
if ( Character.class.isAssignableFrom( type ) || type == Object.class ) {
return (X) value;
}
if ( String.class.isAssignableFrom( type ) ) {

View File

@ -51,7 +51,7 @@ public class DoubleJavaType extends AbstractClassJavaType<Double> implements
if ( value == null ) {
return null;
}
if ( Double.class.isAssignableFrom( type ) ) {
if ( Double.class.isAssignableFrom( type ) || type == Object.class ) {
return (X) value;
}
if ( Float.class.isAssignableFrom( type ) ) {

View File

@ -50,7 +50,7 @@ public class FloatJavaType extends AbstractClassJavaType<Float> implements Primi
if ( value == null ) {
return null;
}
if ( Float.class.isAssignableFrom( type ) ) {
if ( Float.class.isAssignableFrom( type ) || type == Object.class ) {
return (X) value;
}
if ( Double.class.isAssignableFrom( type ) ) {

View File

@ -47,7 +47,7 @@ public class IntegerJavaType extends AbstractClassJavaType<Integer>
if ( value == null ) {
return null;
}
if ( Integer.class.isAssignableFrom( type ) ) {
if ( Integer.class.isAssignableFrom( type ) || type == Object.class ) {
return (X) value;
}
if ( Byte.class.isAssignableFrom( type ) ) {

View File

@ -47,7 +47,7 @@ public class LongJavaType extends AbstractClassJavaType<Long>
if ( value == null ) {
return null;
}
if ( Long.class.isAssignableFrom( type ) ) {
if ( Long.class.isAssignableFrom( type ) || type == Object.class ) {
return (X) value;
}
if ( Byte.class.isAssignableFrom( type ) ) {

View File

@ -57,7 +57,7 @@ public class ShortJavaType extends AbstractClassJavaType<Short>
if ( value == null ) {
return null;
}
if ( Short.class.isAssignableFrom( type ) ) {
if ( Short.class.isAssignableFrom( type ) || type == Object.class ) {
return (X) value;
}
if ( Byte.class.isAssignableFrom( type ) ) {

View File

@ -20,7 +20,6 @@ import org.hibernate.dialect.StructHelper;
import org.hibernate.engine.jdbc.Size;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
import org.hibernate.type.BasicPluralType;
import org.hibernate.type.descriptor.ValueBinder;
import org.hibernate.type.descriptor.ValueExtractor;
@ -107,23 +106,58 @@ public class ArrayJdbcType implements JdbcType {
@Override
public Class<?> getPreferredJavaTypeClass(WrapperOptions options) {
return java.sql.Array.class;
return Object[].class;
}
protected Object[] getArray(BasicBinder<?> binder, Object value, WrapperOptions options) throws SQLException {
protected String getElementTypeName(JavaType<?> javaType, SharedSessionContractImplementor session) {
// TODO: ideally, we would have the actual size or the actual type/column accessible
// this is something that we would need for supporting composite types anyway
if ( elementJdbcType instanceof StructJdbcType ) {
return ( (StructJdbcType) elementJdbcType ).getStructTypeName();
}
final JavaType<?> elementJavaType;
if ( javaType instanceof ByteArrayJavaType ) {
// Special handling needed for Byte[], because that would conflict with the VARBINARY mapping
elementJavaType = ByteJavaType.INSTANCE;
}
else {
elementJavaType = ( (BasicPluralJavaType<?>) javaType ).getElementJavaType();
}
final Size size = session.getJdbcServices()
.getDialect()
.getSizeStrategy()
.resolveSize( elementJdbcType, elementJavaType, null, null, null );
final DdlTypeRegistry ddlTypeRegistry = session.getTypeConfiguration().getDdlTypeRegistry();
final String typeName = ddlTypeRegistry.getDescriptor( elementJdbcType.getDdlTypeCode() )
.getTypeName( size, new BasicTypeImpl<>( elementJavaType, elementJdbcType), ddlTypeRegistry );
int cutIndex = typeName.indexOf( '(' );
if ( cutIndex > 0 ) {
// getTypeName for this case required length, etc, parameters.
// Cut them out and use database defaults.
return typeName.substring( 0, cutIndex );
}
else {
return typeName;
}
}
protected Object[] getArray(
BasicBinder<?> binder,
ValueBinder<Object> elementBinder,
Object value,
WrapperOptions options) throws SQLException {
final JdbcType elementJdbcType = ( (ArrayJdbcType) binder.getJdbcType() ).getElementJdbcType();
//noinspection unchecked
final JavaType<Object> javaType = (JavaType<Object>) binder.getJavaType();
if ( elementJdbcType instanceof AggregateJdbcType ) {
final AggregateJdbcType aggregateJdbcType = (AggregateJdbcType) elementJdbcType;
final Object[] domainObjects = ( javaType ).unwrap(
final Object[] domainObjects = javaType.unwrap(
value,
Object[].class,
options
);
final Object[] objects = new Object[domainObjects.length];
for ( int i = 0; i < domainObjects.length; i++ ) {
objects[i] = aggregateJdbcType.createJdbcValue( domainObjects[i], options );
objects[i] = elementBinder.getBindValue( domainObjects[i], options );
}
return objects;
}
@ -174,6 +208,8 @@ public class ArrayJdbcType implements JdbcType {
@Override
public <X> ValueBinder<X> getBinder(final JavaType<X> javaTypeDescriptor) {
//noinspection unchecked
final ValueBinder<Object> elementBinder = elementJdbcType.getBinder( ( (BasicPluralJavaType<Object>) javaTypeDescriptor ).getElementJavaType() );
return new BasicBinder<>( javaTypeDescriptor, this ) {
@Override
@ -193,49 +229,20 @@ public class ArrayJdbcType implements JdbcType {
}
}
@Override
public Object getBindValue(X value, WrapperOptions options) throws SQLException {
return ( (ArrayJdbcType) getJdbcType() ).getArray( this, elementBinder, value, options );
}
private java.sql.Array getArray(X value, WrapperOptions options) throws SQLException {
final JdbcType elementJdbcType = ( (ArrayJdbcType) getJdbcType() ).getElementJdbcType();
final Object[] objects = ArrayJdbcType.this.getArray( this, value, options );
final ArrayJdbcType arrayJdbcType = (ArrayJdbcType) getJdbcType();
final Object[] objects = arrayJdbcType.getArray( this, elementBinder, value, options );
final SharedSessionContractImplementor session = options.getSession();
final String typeName = getElementTypeName( elementJdbcType, session );
final String typeName = arrayJdbcType.getElementTypeName( getJavaType(), session );
return session.getJdbcCoordinator().getLogicalConnection().getPhysicalConnection()
.createArrayOf( typeName, objects );
}
private String getElementTypeName(JdbcType elementJdbcType, SharedSessionContractImplementor session) {
// TODO: ideally, we would have the actual size or the actual type/column accessible
// this is something that we would need for supporting composite types anyway
if ( elementJdbcType instanceof StructJdbcType ) {
return ( (StructJdbcType) elementJdbcType ).getStructTypeName();
}
final JavaType<X> elementJavaType;
if ( getJavaType() instanceof ByteArrayJavaType ) {
// Special handling needed for Byte[], because that would conflict with the VARBINARY mapping
//noinspection unchecked
elementJavaType = (JavaType<X>) ByteJavaType.INSTANCE;
}
else {
//noinspection unchecked
elementJavaType = ( (BasicPluralJavaType<X>) getJavaType() ).getElementJavaType();
}
final Size size = session.getJdbcServices()
.getDialect()
.getSizeStrategy()
.resolveSize( elementJdbcType, elementJavaType, null, null, null );
final DdlTypeRegistry ddlTypeRegistry = session.getTypeConfiguration().getDdlTypeRegistry();
final String typeName = ddlTypeRegistry.getDescriptor( elementJdbcType.getDdlTypeCode() )
.getTypeName( size, new BasicTypeImpl<>( elementJavaType, elementJdbcType), ddlTypeRegistry );
int cutIndex = typeName.indexOf( '(' );
if ( cutIndex > 0 ) {
// getTypeName for this case required length, etc, parameters.
// Cut them out and use database defaults.
return typeName.substring( 0, cutIndex );
}
else {
return typeName;
}
}
};
}
@ -266,7 +273,7 @@ public class ArrayJdbcType implements JdbcType {
@Override
public String toString() {
return "ArrayTypeDescriptor";
return "ArrayTypeDescriptor(" + getElementJdbcType().toString() + ")";
}
/**

View File

@ -20,18 +20,21 @@ import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
@JiraKey( "HHH-15831" )
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsStructAggregate.class)
public class StructComponentCollectionErrorTest {
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsStructuralArrays.class)
public class StructComponentAssociationErrorTest {
@Test
@JiraKey( "HHH-15831" )
public void testError1() {
public void testOneToOneMappedBy() {
final StandardServiceRegistry ssr = ServiceRegistryUtil.serviceRegistry();
try {
new MetadataSources( ssr )
@ -41,7 +44,7 @@ public class StructComponentCollectionErrorTest {
Assertions.fail( "Expected a failure" );
}
catch (MappingException ex) {
assertThat( ex.getMessage(), containsString( "author.favoriteBook" ) );
assertThat( ex.getMessage(), containsString( "authors.favoriteBook" ) );
}
finally {
StandardServiceRegistryBuilder.destroy( ssr );
@ -56,19 +59,21 @@ public class StructComponentCollectionErrorTest {
@GeneratedValue
private Long id;
private String title;
private Person1 author;
private Person1[] authors;
@OneToOne(fetch = FetchType.LAZY)
private Book1 favoredBook;
}
@Embeddable
@Struct(name = "person_type")
public static class Person1 {
private String name;
@ManyToOne(fetch = FetchType.LAZY)
@OneToOne(mappedBy = "favoredBook", fetch = FetchType.LAZY)
private Book1 favoriteBook;
}
@Test
@JiraKey( "HHH-15831" )
public void testError2() {
public void testOneToManyMappedBy() {
final StandardServiceRegistry ssr = ServiceRegistryUtil.serviceRegistry();
try {
new MetadataSources( ssr )
@ -78,7 +83,7 @@ public class StructComponentCollectionErrorTest {
Assertions.fail( "Expected a failure" );
}
catch (MappingException ex) {
assertThat( ex.getMessage(), containsString( "author.bookCollection" ) );
assertThat( ex.getMessage(), containsString( "authors.bookCollection" ) );
}
finally {
StandardServiceRegistryBuilder.destroy( ssr );
@ -93,15 +98,54 @@ public class StructComponentCollectionErrorTest {
@GeneratedValue
private Long id;
private String title;
private Person2 author;
private Person2[] authors;
@ManyToOne(fetch = FetchType.LAZY)
private Book2 mainBook;
}
@Embeddable
@Struct(name = "person_type")
public static class Person2 {
private String name;
@OneToMany
@OneToMany(mappedBy = "mainBook")
private List<Book2> bookCollection;
}
@Test
public void testOneToMany() {
final StandardServiceRegistry ssr = ServiceRegistryUtil.serviceRegistry();
try {
new MetadataSources( ssr )
.addAnnotatedClass( Book3.class )
.getMetadataBuilder()
.build();
Assertions.fail( "Expected a failure" );
}
catch (MappingException ex) {
assertThat( ex.getMessage(), containsString( "authors.bookCollection" ) );
}
finally {
StandardServiceRegistryBuilder.destroy( ssr );
}
}
@Entity(name = "Book")
public static class Book3 {
@Id
@GeneratedValue
private Long id;
private String title;
private Person3[] authors;
}
@Embeddable
@Struct(name = "person_type")
public static class Person3 {
private String name;
@OneToMany
private List<Book3> bookCollection;
}
}

View File

@ -0,0 +1,153 @@
package org.hibernate.orm.test.component;
import org.hibernate.Hibernate;
import org.hibernate.annotations.Any;
import org.hibernate.annotations.AnyDiscriminatorValue;
import org.hibernate.annotations.AnyKeyJavaClass;
import org.hibernate.annotations.Struct;
import org.hibernate.testing.jdbc.SharedDriverManagerTypeCacheClearingIntegrator;
import org.hibernate.testing.orm.junit.BootstrapServiceRegistry;
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@BootstrapServiceRegistry(
// Clear the type cache, otherwise we might run into ORA-21700: object does not exist or is marked for delete
integrators = SharedDriverManagerTypeCacheClearingIntegrator.class
)
@DomainModel(
annotatedClasses = {
StructComponentManyToAnyTest.Book.class
}
)
@SessionFactory
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsStructAggregate.class)
public class StructComponentManyToAnyTest {
@BeforeEach
public void setUp(SessionFactoryScope scope){
scope.inTransaction(
session -> {
Book book1 = new Book();
book1.id = 1L;
book1.title = "Hibernate 3";
book1.author = new Author( "Gavin", null );
session.save( book1 );
Book book2 = new Book();
book2.id = 2L;
book2.title = "Hibernate 6";
book2.author = new Author( "Steve", book1 );
session.save( book2 );
}
);
}
@AfterEach
public void tearDown(SessionFactoryScope scope){
scope.inTransaction(
session ->
session.createQuery( "delete from Book" ).executeUpdate()
);
}
@Test
public void testGet(SessionFactoryScope scope){
scope.inTransaction(
session -> {
Book book = session.createQuery( "from Book b where b.id = 2", Book.class ).getSingleResult();
assertFalse( Hibernate.isInitialized( book.author.getFavoriteBook() ) );
assertEquals( "Gavin", book.author.getFavoriteBook().getAuthor().getName() );
}
);
}
@Entity(name = "Book")
public static class Book {
@Id
private Long id;
private String title;
private Author author;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Author getAuthor() {
return author;
}
public void setAuthor(Author author) {
this.author = author;
}
}
@Embeddable
@Struct( name = "author_type")
public static class Author {
private String name;
@Any(fetch = FetchType.LAZY)
@AnyKeyJavaClass(Long.class)
@AnyDiscriminatorValue(entity = Book.class, discriminator = "B")
@JoinColumn(name = "favorite_book_id")
@Column(name = "favorite_book_type")
private Object favoriteBook;
public Author() {
}
public Author(String name, Object favoriteBook) {
this.name = name;
this.favoriteBook = favoriteBook;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Book getFavoriteBook() {
return (Book) favoriteBook;
}
public void setFavoriteBook(Book favoriteBook) {
this.favoriteBook = favoriteBook;
}
}
}

View File

@ -0,0 +1,173 @@
package org.hibernate.orm.test.component;
import java.util.Set;
import org.hibernate.Hibernate;
import org.hibernate.annotations.Struct;
import org.hibernate.testing.jdbc.SharedDriverManagerTypeCacheClearingIntegrator;
import org.hibernate.testing.orm.junit.BootstrapServiceRegistry;
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
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.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@BootstrapServiceRegistry(
// Clear the type cache, otherwise we might run into ORA-21700: object does not exist or is marked for delete
integrators = SharedDriverManagerTypeCacheClearingIntegrator.class
)
@DomainModel(
annotatedClasses = {
StructComponentManyToManyMappedByTest.Book.class
}
)
@SessionFactory
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsStructAggregate.class)
public class StructComponentManyToManyMappedByTest {
@BeforeEach
public void setUp(SessionFactoryScope scope){
scope.inTransaction(
session -> {
Book book1 = new Book();
book1.id = 1L;
book1.title = "Main book";
book1.author = new Author( "Abc" );
session.save( book1 );
Book book2 = new Book();
book2.id = 2L;
book2.title = "Second book";
book2.booksInSeries = Set.of( book1 );
book2.author = new Author( "Abc" );
session.save( book2 );
}
);
}
@AfterEach
public void tearDown(SessionFactoryScope scope){
scope.inTransaction(
session ->
session.createQuery( "delete from Book" ).executeUpdate()
);
}
@Test
public void testGet(SessionFactoryScope scope){
scope.inTransaction(
session -> {
Book book = session.createQuery( "from Book b where b.id = 1", Book.class ).getSingleResult();
assertFalse( Hibernate.isInitialized( book.author.getBooks() ) );
assertEquals( "Second book", book.author.getBooks().iterator().next().getTitle() );
}
);
}
@Test
public void testJoin(SessionFactoryScope scope){
scope.inTransaction(
session -> {
Book book = session.createQuery(
"from Book b join fetch b.author.books where b.id = 1",
Book.class
).getSingleResult();
assertTrue( Hibernate.isInitialized( book.author.getBooks() ) );
assertEquals( "Second book", book.author.getBooks().iterator().next().getTitle() );
}
);
}
@Entity(name = "Book")
public static class Book {
@Id
private Long id;
private String title;
private Author author;
@ManyToMany
private Set<Book> booksInSeries;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Author getAuthor() {
return author;
}
public void setAuthor(Author author) {
this.author = author;
}
public Set<Book> getBooksInSeries() {
return booksInSeries;
}
public void setBooksInSeries(Set<Book> booksInSeries) {
this.booksInSeries = booksInSeries;
}
}
@Embeddable
@Struct( name = "author_type")
public static class Author {
private String name;
@ManyToMany(mappedBy = "booksInSeries")
private Set<Book> books;
public Author() {
}
public Author(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Book> getBooks() {
return books;
}
public void setBooks(Set<Book> books) {
this.books = books;
}
}
}

View File

@ -0,0 +1,161 @@
package org.hibernate.orm.test.component;
import java.util.Set;
import org.hibernate.Hibernate;
import org.hibernate.annotations.Struct;
import org.hibernate.testing.jdbc.SharedDriverManagerTypeCacheClearingIntegrator;
import org.hibernate.testing.orm.junit.BootstrapServiceRegistry;
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
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.ManyToMany;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@BootstrapServiceRegistry(
// Clear the type cache, otherwise we might run into ORA-21700: object does not exist or is marked for delete
integrators = SharedDriverManagerTypeCacheClearingIntegrator.class
)
@DomainModel(
annotatedClasses = {
StructComponentManyToManyTest.Book.class
}
)
@SessionFactory
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsStructAggregate.class)
public class StructComponentManyToManyTest {
@BeforeEach
public void setUp(SessionFactoryScope scope){
scope.inTransaction(
session -> {
Book book1 = new Book();
book1.id = 1L;
book1.title = "Main book";
book1.author = new Author( "Abc", null );
session.save( book1 );
Book book2 = new Book();
book2.id = 2L;
book2.title = "Second book";
book2.author = new Author( "Abc", book1 );
session.save( book2 );
}
);
}
@AfterEach
public void tearDown(SessionFactoryScope scope){
scope.inTransaction(
session ->
session.createQuery( "delete from Book" ).executeUpdate()
);
}
@Test
public void testGet(SessionFactoryScope scope){
scope.inTransaction(
session -> {
Book book = session.createQuery( "from Book b where b.id = 2", Book.class ).getSingleResult();
assertFalse( Hibernate.isInitialized( book.author.getBooks() ) );
assertEquals( "Main book", book.author.getBooks().iterator().next().getTitle() );
}
);
}
@Test
public void testJoin(SessionFactoryScope scope){
scope.inTransaction(
session -> {
Book book = session.createQuery(
"from Book b join fetch b.author.books where b.id = 2",
Book.class
).getSingleResult();
assertTrue( Hibernate.isInitialized( book.author.getBooks() ) );
assertEquals( "Main book", book.author.getBooks().iterator().next().getTitle() );
}
);
}
@Entity(name = "Book")
public static class Book {
@Id
private Long id;
private String title;
private Author author;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Author getAuthor() {
return author;
}
public void setAuthor(Author author) {
this.author = author;
}
}
@Embeddable
@Struct( name = "author_type")
public static class Author {
private String name;
@ManyToMany
private Set<Book> books;
public Author() {
}
public Author(String name, Book book) {
this.name = name;
this.books = book == null ? null : Set.of( book );
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Book> getBooks() {
return books;
}
public void setBooks(Set<Book> books) {
this.books = books;
}
}
}

View File

@ -0,0 +1,159 @@
package org.hibernate.orm.test.component;
import org.hibernate.Hibernate;
import org.hibernate.annotations.Struct;
import org.hibernate.testing.jdbc.SharedDriverManagerTypeCacheClearingIntegrator;
import org.hibernate.testing.orm.junit.BootstrapServiceRegistry;
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
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.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@BootstrapServiceRegistry(
// Clear the type cache, otherwise we might run into ORA-21700: object does not exist or is marked for delete
integrators = SharedDriverManagerTypeCacheClearingIntegrator.class
)
@DomainModel(
annotatedClasses = {
StructComponentManyToOneTest.Book.class
}
)
@SessionFactory
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsStructAggregate.class)
public class StructComponentManyToOneTest {
@BeforeEach
public void setUp(SessionFactoryScope scope){
scope.inTransaction(
session -> {
Book book1 = new Book();
book1.id = 1L;
book1.title = "Hibernate 3";
book1.author = new Author( "Gavin", null );
session.save( book1 );
Book book2 = new Book();
book2.id = 2L;
book2.title = "Hibernate 6";
book2.author = new Author( "Steve", book1 );
session.save( book2 );
}
);
}
@AfterEach
public void tearDown(SessionFactoryScope scope){
scope.inTransaction(
session ->
session.createQuery( "delete from Book" ).executeUpdate()
);
}
@Test
public void testGet(SessionFactoryScope scope){
scope.inTransaction(
session -> {
Book book = session.createQuery( "from Book b where b.id = 2", Book.class ).getSingleResult();
assertFalse( Hibernate.isInitialized( book.author.getFavoriteBook() ) );
assertEquals( "Gavin", book.author.getFavoriteBook().getAuthor().getName() );
}
);
}
@Test
public void testJoin(SessionFactoryScope scope){
scope.inTransaction(
session -> {
Book book = session.createQuery(
"from Book b join fetch b.author.favoriteBook where b.id = 2",
Book.class
).getSingleResult();
assertTrue( Hibernate.isInitialized( book.author.getFavoriteBook() ) );
assertEquals( "Gavin", book.author.getFavoriteBook().getAuthor().getName() );
}
);
}
@Entity(name = "Book")
public static class Book {
@Id
private Long id;
private String title;
private Author author;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Author getAuthor() {
return author;
}
public void setAuthor(Author author) {
this.author = author;
}
}
@Embeddable
@Struct( name = "author_type")
public static class Author {
private String name;
@ManyToOne(fetch = FetchType.LAZY)
private Book favoriteBook;
public Author() {
}
public Author(String name, Book favoriteBook) {
this.name = name;
this.favoriteBook = favoriteBook;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Book getFavoriteBook() {
return favoriteBook;
}
public void setFavoriteBook(Book favoriteBook) {
this.favoriteBook = favoriteBook;
}
}
}

View File

@ -0,0 +1,173 @@
package org.hibernate.orm.test.component;
import java.util.Set;
import org.hibernate.Hibernate;
import org.hibernate.annotations.Struct;
import org.hibernate.testing.jdbc.SharedDriverManagerTypeCacheClearingIntegrator;
import org.hibernate.testing.orm.junit.BootstrapServiceRegistry;
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
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.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@BootstrapServiceRegistry(
// Clear the type cache, otherwise we might run into ORA-21700: object does not exist or is marked for delete
integrators = SharedDriverManagerTypeCacheClearingIntegrator.class
)
@DomainModel(
annotatedClasses = {
StructComponentOneToManyMappedByTest.Book.class
}
)
@SessionFactory
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsStructAggregate.class)
public class StructComponentOneToManyMappedByTest {
@BeforeEach
public void setUp(SessionFactoryScope scope){
scope.inTransaction(
session -> {
Book book1 = new Book();
book1.id = 1L;
book1.title = "Main book";
book1.author = new Author( "Abc" );
session.save( book1 );
Book book2 = new Book();
book2.id = 2L;
book2.title = "Second book";
book2.mainBook = book1;
book2.author = new Author( "Abc" );
session.save( book2 );
}
);
}
@AfterEach
public void tearDown(SessionFactoryScope scope){
scope.inTransaction(
session ->
session.createQuery( "delete from Book" ).executeUpdate()
);
}
@Test
public void testGet(SessionFactoryScope scope){
scope.inTransaction(
session -> {
Book book = session.createQuery( "from Book b where b.id = 1", Book.class ).getSingleResult();
assertFalse( Hibernate.isInitialized( book.author.getBooks() ) );
assertEquals( "Second book", book.author.getBooks().iterator().next().getTitle() );
}
);
}
@Test
public void testJoin(SessionFactoryScope scope){
scope.inTransaction(
session -> {
Book book = session.createQuery(
"from Book b join fetch b.author.books where b.id = 1",
Book.class
).getSingleResult();
assertTrue( Hibernate.isInitialized( book.author.getBooks() ) );
assertEquals( "Second book", book.author.getBooks().iterator().next().getTitle() );
}
);
}
@Entity(name = "Book")
public static class Book {
@Id
private Long id;
private String title;
private Author author;
@ManyToOne(fetch = FetchType.LAZY)
private Book mainBook;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Author getAuthor() {
return author;
}
public void setAuthor(Author author) {
this.author = author;
}
public Book getMainBook() {
return mainBook;
}
public void setMainBook(Book mainBook) {
this.mainBook = mainBook;
}
}
@Embeddable
@Struct( name = "author_type")
public static class Author {
private String name;
@OneToMany(mappedBy = "mainBook")
private Set<Book> books;
public Author() {
}
public Author(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Book> getBooks() {
return books;
}
public void setBooks(Set<Book> books) {
this.books = books;
}
}
}

View File

@ -0,0 +1,161 @@
package org.hibernate.orm.test.component;
import java.util.Set;
import org.hibernate.Hibernate;
import org.hibernate.annotations.Struct;
import org.hibernate.testing.jdbc.SharedDriverManagerTypeCacheClearingIntegrator;
import org.hibernate.testing.orm.junit.BootstrapServiceRegistry;
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
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.JoinColumn;
import jakarta.persistence.OneToMany;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@BootstrapServiceRegistry(
// Clear the type cache, otherwise we might run into ORA-21700: object does not exist or is marked for delete
integrators = SharedDriverManagerTypeCacheClearingIntegrator.class
)
@DomainModel(
annotatedClasses = {
StructComponentOneToManyTest.Book.class
}
)
@SessionFactory
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsStructAggregate.class)
public class StructComponentOneToManyTest {
@BeforeEach
public void setUp(SessionFactoryScope scope){
scope.inTransaction(
session -> {
Book book1 = new Book();
book1.id = 1L;
book1.title = "Hibernate 3";
book1.author = new Author( "Gavin", null );
session.save( book1 );
Book book2 = new Book();
book2.id = 2L;
book2.title = "Hibernate 6";
book2.author = new Author( "Steve", book1 );
session.save( book2 );
}
);
}
@AfterEach
public void tearDown(SessionFactoryScope scope){
scope.inTransaction(
session ->
session.createQuery( "delete from Book" ).executeUpdate()
);
}
@Test
public void testGet(SessionFactoryScope scope){
scope.inTransaction(
session -> {
Book book = session.createQuery( "from Book b where b.id = 2", Book.class ).getSingleResult();
assertFalse( Hibernate.isInitialized( book.author.getBooks() ) );
assertEquals( "Gavin", book.author.getBooks().iterator().next().getAuthor().getName() );
}
);
}
@Test
public void testJoin(SessionFactoryScope scope){
scope.inTransaction(
session -> {
Book book = session.createQuery(
"from Book b join fetch b.author.books where b.id = 2",
Book.class
).getSingleResult();
assertTrue( Hibernate.isInitialized( book.author.getBooks() ) );
assertEquals( "Gavin", book.author.getBooks().iterator().next().getAuthor().getName() );
}
);
}
@Entity(name = "Book")
public static class Book {
@Id
private Long id;
private String title;
private Author author;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Author getAuthor() {
return author;
}
public void setAuthor(Author author) {
this.author = author;
}
}
@Embeddable
@Struct( name = "author_type")
public static class Author {
private String name;
@OneToMany
private Set<Book> books;
public Author() {
}
public Author(String name, Book book) {
this.name = name;
this.books = book == null ? null : Set.of( book );
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Book> getBooks() {
return books;
}
public void setBooks(Set<Book> books) {
this.books = books;
}
}
}

View File

@ -0,0 +1,190 @@
package org.hibernate.orm.test.component;
import org.hibernate.Hibernate;
import org.hibernate.annotations.Struct;
import org.hibernate.testing.jdbc.SharedDriverManagerTypeCacheClearingIntegrator;
import org.hibernate.testing.orm.junit.BootstrapServiceRegistry;
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
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.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.OneToOne;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@BootstrapServiceRegistry(
// Clear the type cache, otherwise we might run into ORA-21700: object does not exist or is marked for delete
integrators = SharedDriverManagerTypeCacheClearingIntegrator.class
)
@DomainModel(
annotatedClasses = {
StructComponentOneToOneMappedByTest.Book.class,
StructComponentOneToOneMappedByTest.BookDetails.class
}
)
@SessionFactory
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsStructAggregate.class)
public class StructComponentOneToOneMappedByTest {
@BeforeEach
public void setUp(SessionFactoryScope scope){
scope.inTransaction(
session -> {
Book book = new Book();
BookDetails bookDetails = new BookDetails(book, "A nice book");
book.id = 1L;
book.title = "Hibernate 6";
book.author = new Author( "Steve", bookDetails );
session.save( book );
session.save( bookDetails );
}
);
}
@AfterEach
public void tearDown(SessionFactoryScope scope){
scope.inTransaction(
session -> {
session.createQuery( "delete from BookDetails" ).executeUpdate();
session.createQuery( "delete from Book" ).executeUpdate();
}
);
}
@Test
public void testGet(SessionFactoryScope scope){
scope.inTransaction(
session -> {
Book book = session.createQuery( "from Book b", Book.class ).getSingleResult();
// One-to-one mappedBy is eager by default
assertTrue( Hibernate.isInitialized( book.author.getDetails() ) );
assertEquals( "A nice book", book.author.getDetails().getSummary() );
}
);
}
@Test
public void testJoin(SessionFactoryScope scope){
scope.inTransaction(
session -> {
Book book = session.createQuery(
"from Book b join fetch b.author.details",
Book.class
).getSingleResult();
assertTrue( Hibernate.isInitialized( book.author.getDetails() ) );
assertEquals( "A nice book", book.author.getDetails().getSummary() );
}
);
}
@Entity(name = "Book")
public static class Book {
@Id
private Long id;
private String title;
private Author author;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Author getAuthor() {
return author;
}
public void setAuthor(Author author) {
this.author = author;
}
}
@Embeddable
@Struct( name = "author_type")
public static class Author {
private String name;
@OneToOne(mappedBy = "book", fetch = FetchType.LAZY)
private BookDetails details;
public Author() {
}
public Author(String name, BookDetails details) {
this.name = name;
this.details = details;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public BookDetails getDetails() {
return details;
}
public void setDetails(BookDetails details) {
this.details = details;
}
}
@Entity(name = "BookDetails")
public static class BookDetails {
@Id
@OneToOne
private Book book;
private String summary;
public BookDetails() {
}
public BookDetails(Book book, String summary) {
this.book = book;
this.summary = summary;
}
public Book getBook() {
return book;
}
public void setBook(Book book) {
this.book = book;
}
public String getSummary() {
return summary;
}
public void setSummary(String summary) {
this.summary = summary;
}
}
}

View File

@ -0,0 +1,161 @@
package org.hibernate.orm.test.component;
import java.util.List;
import org.hibernate.Hibernate;
import org.hibernate.annotations.Struct;
import org.hibernate.testing.jdbc.SharedDriverManagerTypeCacheClearingIntegrator;
import org.hibernate.testing.orm.junit.BootstrapServiceRegistry;
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
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.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.OneToOne;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@BootstrapServiceRegistry(
// Clear the type cache, otherwise we might run into ORA-21700: object does not exist or is marked for delete
integrators = SharedDriverManagerTypeCacheClearingIntegrator.class
)
@DomainModel(
annotatedClasses = {
StructComponentOneToOneTest.Book.class
}
)
@SessionFactory
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsStructAggregate.class)
public class StructComponentOneToOneTest {
@BeforeEach
public void setUp(SessionFactoryScope scope){
scope.inTransaction(
session -> {
Book book1 = new Book();
book1.id = 1L;
book1.title = "Hibernate 3";
book1.author = new Author( "Gavin", null );
session.save( book1 );
Book book2 = new Book();
book2.id = 2L;
book2.title = "Hibernate 6";
book2.author = new Author( "Steve", book1 );
session.save( book2 );
}
);
}
@AfterEach
public void tearDown(SessionFactoryScope scope){
scope.inTransaction(
session ->
session.createQuery( "delete from Book" ).executeUpdate()
);
}
@Test
public void testGet(SessionFactoryScope scope){
scope.inTransaction(
session -> {
Book book = session.createQuery( "from Book b where b.id = 2", Book.class ).getSingleResult();
assertFalse( Hibernate.isInitialized( book.author.getFavoriteBook() ) );
assertEquals( "Gavin", book.author.getFavoriteBook().getAuthor().getName() );
}
);
}
@Test
public void testJoin(SessionFactoryScope scope){
scope.inTransaction(
session -> {
Book book = session.createQuery(
"from Book b join fetch b.author.favoriteBook where b.id = 2",
Book.class
).getSingleResult();
assertTrue( Hibernate.isInitialized( book.author.getFavoriteBook() ) );
assertEquals( "Gavin", book.author.getFavoriteBook().getAuthor().getName() );
}
);
}
@Entity(name = "Book")
public static class Book {
@Id
private Long id;
private String title;
private Author author;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Author getAuthor() {
return author;
}
public void setAuthor(Author author) {
this.author = author;
}
}
@Embeddable
@Struct( name = "author_type")
public static class Author {
private String name;
@OneToOne(fetch = FetchType.LAZY)
private Book favoriteBook;
public Author() {
}
public Author(String name, Book favoriteBook) {
this.name = name;
this.favoriteBook = favoriteBook;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Book getFavoriteBook() {
return favoriteBook;
}
public void setFavoriteBook(Book favoriteBook) {
this.favoriteBook = favoriteBook;
}
}
}

View File

@ -0,0 +1,169 @@
package org.hibernate.orm.test.component;
import java.util.List;
import org.hibernate.MappingException;
import org.hibernate.annotations.Struct;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
import org.hibernate.testing.orm.junit.JiraKey;
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
import org.hibernate.testing.util.ServiceRegistryUtil;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import jakarta.persistence.Embeddable;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
@JiraKey( "HHH-15831" )
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsStructAggregate.class)
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsStructuralArrays.class)
public class StructNestedComponentAssociationErrorTest {
@Test
public void testOneToOneMappedByNested() {
final StandardServiceRegistry ssr = ServiceRegistryUtil.serviceRegistry();
try {
new MetadataSources( ssr )
.addAnnotatedClass( Book1.class )
.getMetadataBuilder()
.build();
Assertions.fail( "Expected a failure" );
}
catch (MappingException ex) {
assertThat( ex.getMessage(), containsString( "authorInfos.person.favoriteBook" ) );
}
finally {
StandardServiceRegistryBuilder.destroy( ssr );
}
}
@Entity(name = "Book")
public static class Book1 {
@Id
@GeneratedValue
private Long id;
private String title;
private AuthorInfo[] authorInfos;
@OneToOne(fetch = FetchType.LAZY)
private Book1 favoredBook;
}
@Embeddable
@Struct(name = "author_info_type")
public static class AuthorInfo {
Person1 person;
}
@Embeddable
@Struct(name = "person_type")
public static class Person1 {
private String name;
@OneToOne(mappedBy = "favoredBook", fetch = FetchType.LAZY)
private Book1 favoriteBook;
}
@Test
public void testOneToManyMappedByNested() {
final StandardServiceRegistry ssr = ServiceRegistryUtil.serviceRegistry();
try {
new MetadataSources( ssr )
.addAnnotatedClass( Book2.class )
.getMetadataBuilder()
.build();
Assertions.fail( "Expected a failure" );
}
catch (MappingException ex) {
assertThat( ex.getMessage(), containsString( "authorInfos.person.bookCollection" ) );
}
finally {
StandardServiceRegistryBuilder.destroy( ssr );
}
}
@Entity(name = "Book")
public static class Book2 {
@Id
@GeneratedValue
private Long id;
private String title;
private AuthorInfo2[] authorInfos;
@ManyToOne(fetch = FetchType.LAZY)
private Book2 mainBook;
}
@Embeddable
@Struct(name = "author_info_type")
public static class AuthorInfo2 {
Person2 person;
}
@Embeddable
@Struct(name = "person_type")
public static class Person2 {
private String name;
@OneToMany(mappedBy = "mainBook")
private List<Book2> bookCollection;
}
@Test
public void testOneToMany() {
final StandardServiceRegistry ssr = ServiceRegistryUtil.serviceRegistry();
try {
new MetadataSources( ssr )
.addAnnotatedClass( Book3.class )
.getMetadataBuilder()
.build();
Assertions.fail( "Expected a failure" );
}
catch (MappingException ex) {
assertThat( ex.getMessage(), containsString( "authorInfos.person.bookCollection" ) );
}
finally {
StandardServiceRegistryBuilder.destroy( ssr );
}
}
@Entity(name = "Book")
public static class Book3 {
@Id
@GeneratedValue
private Long id;
private String title;
private AuthorInfo3[] authorInfos;
}
@Embeddable
@Struct(name = "author_info_type")
public static class AuthorInfo3 {
Person3 person;
}
@Embeddable
@Struct(name = "person_type")
public static class Person3 {
private String name;
@OneToMany
private List<Book3> bookCollection;
}
}