HHH-16957 Embeddable cannot have only one @generated field

This commit is contained in:
Andrea Boriero 2024-11-15 13:11:53 +01:00
parent 9e7a2d0f33
commit 74422feead
11 changed files with 205 additions and 70 deletions

View File

@ -0,0 +1,9 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.generator;
public interface CompositeOnExecutionGenerator extends OnExecutionGenerator {
OnExecutionGenerator getPropertyGenerator(String propertyName);
}

View File

@ -20,6 +20,8 @@ public abstract class AbstractSingularAttributeMapping
extends AbstractStateArrayContributorMapping
implements SingularAttributeMapping {
private Generator generator;
public AbstractSingularAttributeMapping(
String name,
int stateArrayPosition,
@ -52,7 +54,20 @@ public abstract class AbstractSingularAttributeMapping
@Override
public Generator getGenerator() {
return findContainingEntityMapping().getEntityPersister().getEntityMetamodel().getGenerators()[getStateArrayPosition()];
if ( generator != null ) {
return generator;
}
final int stateArrayPosition = getStateArrayPosition();
if ( stateArrayPosition < 0 ) {
return null;
}
final Generator[] generators = findContainingEntityMapping().getEntityPersister().getEntityMetamodel()
.getGenerators();
if ( generators.length == 0 ) {
return null;
}
generator = generators[stateArrayPosition];
return generator;
}
}

View File

@ -24,6 +24,9 @@ import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.CascadeStyle;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.generator.CompositeOnExecutionGenerator;
import org.hibernate.generator.Generator;
import org.hibernate.generator.OnExecutionGenerator;
import org.hibernate.mapping.AggregateColumn;
import org.hibernate.mapping.Any;
import org.hibernate.mapping.BasicValue;
@ -171,6 +174,7 @@ public class EmbeddableMappingTypeImpl extends AbstractEmbeddableMapping impleme
private final boolean aggregateMappingRequiresColumnWriter;
private final boolean preferSelectAggregateMapping;
private final boolean preferBindAggregateMapping;
private final CompositeOnExecutionGenerator generator;
private EmbeddableMappingTypeImpl(
Component bootDescriptor,
@ -251,6 +255,7 @@ public class EmbeddableMappingTypeImpl extends AbstractEmbeddableMapping impleme
this.preferSelectAggregateMapping = false;
this.preferBindAggregateMapping = false;
}
this.generator = resolveOnExecutionGenerator( valueMapping );
}
private JdbcMapping resolveJdbcMapping(Component bootDescriptor, RuntimeModelCreationContext creationContext) {
@ -371,6 +376,7 @@ public class EmbeddableMappingTypeImpl extends AbstractEmbeddableMapping impleme
attributeMappings
)
);
this.generator = resolveOnExecutionGenerator( valueMapping );
}
public EmbeddableMappingType createInverseMappingType(
@ -436,13 +442,17 @@ public class EmbeddableMappingTypeImpl extends AbstractEmbeddableMapping impleme
// Reset the attribute mappings that were added in previous attempts
attributeMappings.clear();
for ( final Property bootPropertyDescriptor : bootDescriptor.getProperties() ) {
final AttributeMapping attributeMapping;
final Type subtype = subtypes[attributeIndex];
final Value value = bootPropertyDescriptor.getValue();
final String name = bootPropertyDescriptor.getName();
if ( subtype instanceof BasicType ) {
boolean isInsertable = isInsertable( generator, insertability[columnPosition], name );
boolean isUpdatable = isUpdatable( generator, updateability[columnPosition], name );
final BasicValue basicValue = (BasicValue) value;
final Selectable selectable = dependantValue != null ?
dependantValue.getColumns().get( dependantColumnIndex + columnPosition ) :
@ -474,7 +484,7 @@ public class EmbeddableMappingTypeImpl extends AbstractEmbeddableMapping impleme
containingTableExpression = rootTableExpression;
columnExpression = rootTableKeyColumnNames[columnPosition];
}
final NavigableRole role = valueMapping.getNavigableRole().append( bootPropertyDescriptor.getName() );
final NavigableRole role = valueMapping.getNavigableRole().append( name );
final SelectablePath selectablePath;
final String columnDefinition;
final Long length;
@ -502,10 +512,10 @@ public class EmbeddableMappingTypeImpl extends AbstractEmbeddableMapping impleme
temporalPrecision = null;
isLob = false;
nullable = bootPropertyDescriptor.isOptional();
selectablePath = new SelectablePath( determineEmbeddablePrefix() + bootPropertyDescriptor.getName() );
selectablePath = new SelectablePath( determineEmbeddablePrefix() + name );
}
attributeMapping = MappingModelCreationHelper.buildBasicAttributeMapping(
bootPropertyDescriptor.getName(),
name,
role,
attributeIndex,
attributeIndex,
@ -525,8 +535,8 @@ public class EmbeddableMappingTypeImpl extends AbstractEmbeddableMapping impleme
temporalPrecision,
isLob,
nullable,
insertability[columnPosition],
updateability[columnPosition],
isInsertable,
isUpdatable,
representationStrategy.resolvePropertyAccess( bootPropertyDescriptor ),
compositeType.getCascadeStyle( attributeIndex ),
creationProcess
@ -556,7 +566,7 @@ public class EmbeddableMappingTypeImpl extends AbstractEmbeddableMapping impleme
);
attributeMapping = new DiscriminatedAssociationAttributeMapping(
valueMapping.getNavigableRole().append( bootPropertyDescriptor.getName() ),
valueMapping.getNavigableRole().append( name ),
typeConfiguration.getJavaTypeRegistry().getDescriptor( Object.class ),
this,
attributeIndex,
@ -585,7 +595,7 @@ public class EmbeddableMappingTypeImpl extends AbstractEmbeddableMapping impleme
}
attributeMapping = MappingModelCreationHelper.buildEmbeddedAttributeMapping(
bootPropertyDescriptor.getName(),
name,
attributeIndex,
attributeIndex,
bootPropertyDescriptor,
@ -604,7 +614,7 @@ public class EmbeddableMappingTypeImpl extends AbstractEmbeddableMapping impleme
}
else if ( subtype instanceof CollectionType ) {
attributeMapping = MappingModelCreationHelper.buildPluralAttributeMapping(
bootPropertyDescriptor.getName(),
name,
attributeIndex,
attributeIndex,
bootPropertyDescriptor,
@ -619,8 +629,8 @@ public class EmbeddableMappingTypeImpl extends AbstractEmbeddableMapping impleme
final EntityPersister entityPersister = creationProcess.getEntityPersister( bootDescriptor.getOwner().getEntityName() );
attributeMapping = MappingModelCreationHelper.buildSingularAssociationAttributeMapping(
bootPropertyDescriptor.getName(),
valueMapping.getNavigableRole().append( bootPropertyDescriptor.getName() ),
name,
valueMapping.getNavigableRole().append( name ),
attributeIndex,
attributeIndex,
bootPropertyDescriptor,
@ -639,7 +649,7 @@ public class EmbeddableMappingTypeImpl extends AbstractEmbeddableMapping impleme
Locale.ROOT,
"Unable to determine attribute nature : %s#%s",
bootDescriptor.getOwner().getEntityName(),
bootPropertyDescriptor.getName()
name
)
);
}
@ -667,6 +677,30 @@ public class EmbeddableMappingTypeImpl extends AbstractEmbeddableMapping impleme
return true;
}
private boolean isInsertable(CompositeOnExecutionGenerator generator, boolean propertyInsertability, String propertyName) {
if ( propertyInsertability && generator != null ) {
final OnExecutionGenerator propertyGenerator = generator.getPropertyGenerator( propertyName );
if ( propertyGenerator != null
&& propertyGenerator.generatesOnInsert()
&& !propertyGenerator.writePropertyValue() ) {
return false;
}
}
return propertyInsertability;
}
private boolean isUpdatable(CompositeOnExecutionGenerator generator, boolean propertyUpdatability, String propertyName) {
if ( propertyUpdatability && generator != null ) {
final OnExecutionGenerator propertyGenerator = generator.getPropertyGenerator( propertyName );
if ( propertyGenerator != null
&& propertyGenerator.generatesOnUpdate()
&& !propertyGenerator.writePropertyValue() ) {
return false;
}
}
return propertyUpdatability;
}
private boolean isDefinedInClassOrSuperclass(Component bootDescriptor, String declaringClass, String subclass) {
while ( subclass != null ) {
if ( declaringClass.equals( subclass ) ) {
@ -1121,4 +1155,25 @@ public class EmbeddableMappingTypeImpl extends AbstractEmbeddableMapping impleme
public boolean shouldBindAggregateMapping() {
return preferBindAggregateMapping;
}
private static CompositeOnExecutionGenerator resolveOnExecutionGenerator(EmbeddableValuedModelPart valueMapping) {
if ( valueMapping instanceof EmbeddedAttributeMapping attributeMapping ) {
if ( attributeMapping.getDeclaringType() instanceof EmbeddableMappingTypeImpl embeddableMappingType ) {
if ( embeddableMappingType.getGenerator() instanceof CompositeOnExecutionGenerator compositeOnExecutionGenerator ) {
if ( compositeOnExecutionGenerator.getPropertyGenerator( attributeMapping.getAttributeName() )
instanceof CompositeOnExecutionGenerator composite ) {
return composite;
}
}
}
else if ( attributeMapping.getGenerator() instanceof CompositeOnExecutionGenerator compositeOnExecutionGenerator ) {
return compositeOnExecutionGenerator;
}
}
return null;
}
public Generator getGenerator() {
return generator;
}
}

View File

@ -408,6 +408,11 @@ public class EmbeddedAttributeMapping
return producer.containsTableReference( tableExpression );
}
@Override
public boolean isEntityIdentifierMapping() {
return getAttributeMetadata() instanceof EmbeddedIdentifierMappingImpl;
}
@Override
public int compare(Object value1, Object value2) {
return embeddableMappingType.compare( value1, value2 );

View File

@ -13,6 +13,7 @@ import org.hibernate.engine.spi.EntityKey;
import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.event.spi.MergeContext;
import org.hibernate.generator.Generator;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
@ -332,4 +333,9 @@ public class InverseNonAggregatedIdentifierMapping extends EmbeddedAttributeMapp
public Fetchable getFetchable(int position) {
return getPartMappingType().getFetchable( position );
}
@Override
public Generator getGenerator() {
return null;
}
}

View File

@ -6,6 +6,7 @@ package org.hibernate.metamodel.mapping.internal;
import org.hibernate.engine.FetchStyle;
import org.hibernate.engine.FetchTiming;
import org.hibernate.generator.Generator;
import org.hibernate.metamodel.mapping.AttributeMetadata;
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
@ -97,4 +98,8 @@ public class VirtualEmbeddedAttributeMapping extends EmbeddedAttributeMapping im
);
}
@Override
public Generator getGenerator() {
return null;
}
}

View File

@ -116,16 +116,37 @@ public abstract class AbstractMutationCoordinator {
}
}
protected void handleValueGeneration(
protected void handleInsertableValueGeneration(
AttributeMapping attributeMapping,
MutationGroupBuilder mutationGroupBuilder,
OnExecutionGenerator generator) {
final Dialect dialect = factory.getJdbcServices().getDialect();
final boolean writePropertyValue = generator.writePropertyValue();
final String[] columnValues = writePropertyValue ? null : generator.getReferencedColumnValues( dialect );
attributeMapping.forEachSelectable( (j, mapping) -> {
attributeMapping.forEachInsertable( (j, mapping) -> {
final String tableName = entityPersister.physicalTableNameForMutation( mapping );
final ColumnValuesTableMutationBuilder tableUpdateBuilder = mutationGroupBuilder.findTableDetailsBuilder( tableName );
final ColumnValuesTableMutationBuilder tableUpdateBuilder = mutationGroupBuilder.findTableDetailsBuilder(
tableName );
tableUpdateBuilder.addValueColumn(
mapping.getSelectionExpression(),
writePropertyValue ? "?" : columnValues[j],
mapping.getJdbcMapping(),
mapping.isLob()
);
} );
}
protected void handleUpdatableValueGeneration(
AttributeMapping attributeMapping,
MutationGroupBuilder mutationGroupBuilder,
OnExecutionGenerator generator) {
final Dialect dialect = factory.getJdbcServices().getDialect();
final boolean writePropertyValue = generator.writePropertyValue();
final String[] columnValues = writePropertyValue ? null : generator.getReferencedColumnValues( dialect );
attributeMapping.forEachUpdatable( (j, mapping) -> {
final String tableName = entityPersister.physicalTableNameForMutation( mapping );
final ColumnValuesTableMutationBuilder tableUpdateBuilder = mutationGroupBuilder.findTableDetailsBuilder(
tableName );
tableUpdateBuilder.addValueColumn(
mapping.getSelectionExpression(),
writePropertyValue ? "?" : columnValues[j],

View File

@ -412,7 +412,10 @@ public class InsertCoordinatorStandard extends AbstractMutationCoordinator imple
attributeMapping.forEachInsertable( insertGroupBuilder );
}
else if ( isValueGenerationInSql( generator, factory.getJdbcServices().getDialect() ) ) {
handleValueGeneration( attributeMapping, insertGroupBuilder, (OnExecutionGenerator) generator );
handleInsertableValueGeneration(
attributeMapping,
insertGroupBuilder,
(OnExecutionGenerator) generator );
}
}
}

View File

@ -1220,7 +1220,7 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
SharedSessionContractImplementor session) {
final Generator generator = attributeMapping.getGenerator();
if ( needsValueGeneration( entity, session, generator ) ) {
handleValueGeneration( attributeMapping, updateGroupBuilder, (OnExecutionGenerator) generator );
handleUpdatableValueGeneration( attributeMapping, updateGroupBuilder, (OnExecutionGenerator) generator );
}
else if ( versionMapping != null
&& versionMapping.getVersionAttribute() == attributeMapping) {

View File

@ -8,6 +8,7 @@ import org.hibernate.Internal;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.generator.BeforeExecutionGenerator;
import org.hibernate.generator.CompositeOnExecutionGenerator;
import org.hibernate.generator.EventType;
import org.hibernate.generator.Generator;
import org.hibernate.generator.OnExecutionGenerator;
@ -18,7 +19,9 @@ import org.hibernate.persister.entity.EntityPersister;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static java.lang.System.arraycopy;
import static org.hibernate.generator.EventTypeSets.NONE;
@ -35,7 +38,7 @@ class CompositeGeneratorBuilder {
private boolean hadBeforeExecutionGeneration;
private boolean hadOnExecutionGeneration;
private final List<Generator> generators = new ArrayList<>();
private final Map<String,Generator> generatorsByPropertyName = new HashMap<>();
public CompositeGeneratorBuilder(String entityName, Property mappingProperty, Dialect dialect) {
this.entityName = entityName;
@ -43,8 +46,8 @@ class CompositeGeneratorBuilder {
this.dialect = dialect;
}
public void add(Generator generator) {
generators.add( generator );
public void add(String propertyName, Generator generator) {
generatorsByPropertyName.put( propertyName, generator );
if ( generator != null && generator.generatesSometimes() ) {
if ( generator.generatedOnExecution() ) {
@ -76,7 +79,6 @@ class CompositeGeneratorBuilder {
private OnExecutionGenerator createCompositeOnExecutionGenerator() {
final Component composite = (Component) mappingProperty.getValue();
// the base-line values for the aggregated OnExecutionGenerator we will build here.
final EnumSet<EventType> eventTypes = EnumSet.noneOf(EventType.class);
boolean referenceColumns = false;
@ -87,15 +89,14 @@ class CompositeGeneratorBuilder {
// start building the aggregate values
int columnIndex = 0;
final List<Property> properties = composite.getProperties();
final Map<String, OnExecutionGenerator> generatorsByName = new HashMap<>(properties.size());
for ( int i = 0; i < properties.size(); i++ ) {
final Property property = properties.get(i);
final OnExecutionGenerator generator = (OnExecutionGenerator) generators.get(i);
if ( generator == null ) {
throw new CompositeValueGenerationException(
"Property of on-execution generated embeddable is not generated: "
+ mappingProperty.getName() + '.' + property.getName()
);
}
final String name = property.getName();
final OnExecutionGenerator generator = (OnExecutionGenerator) generatorsByPropertyName.get( name );
generatorsByName.put( name, generator );
if ( generator != null ) {
eventTypes.addAll( generator.getEventTypes() );
if ( generator.referenceColumnsInSql( dialect ) ) {
// override base-line value
@ -106,7 +107,7 @@ class CompositeGeneratorBuilder {
if ( referencedColumnValues.length != span ) {
throw new CompositeValueGenerationException(
"Mismatch between number of collected generated column values and number of columns for composite attribute: "
+ mappingProperty.getName() + '.' + property.getName()
+ mappingProperty.getName() + '.' + name
);
}
arraycopy( referencedColumnValues, 0, columnValues, columnIndex, span );
@ -120,17 +121,20 @@ class CompositeGeneratorBuilder {
mutable = true;
}
}
}
// then use the aggregated values to build an OnExecutionGenerator
return new CompositeOnExecutionGenerator( eventTypes, referenceColumns, columnValues, writable, mutable );
return new CompositeOnExecutionGeneratorImpl( eventTypes, generatorsByName, referenceColumns, columnValues, writable, mutable );
}
private BeforeExecutionGenerator createCompositeBeforeExecutionGenerator() {
final Component composite = (Component) mappingProperty.getValue();
final EnumSet<EventType> eventTypes = EnumSet.noneOf(EventType.class);
final List<Property> properties = composite.getProperties();
final List<Generator> generators = new ArrayList<>(properties.size());
for ( int i = 0; i < properties.size(); i++ ) {
final Generator generator = generators.get(i);
final Generator generator = generatorsByPropertyName.get( properties.get( i ).getName() );
generators.add( generator );
if ( generator != null ) {
eventTypes.addAll( generator.getEventTypes() );
}
@ -138,13 +142,14 @@ class CompositeGeneratorBuilder {
return new CompositeBeforeExecutionGenerator( entityName, generators, mappingProperty, properties, eventTypes );
}
private record CompositeOnExecutionGenerator(
public record CompositeOnExecutionGeneratorImpl(
EnumSet<EventType> eventTypes,
Map<String,OnExecutionGenerator> generatedPropertiesByName,
boolean referenceColumnsInSql,
String[] columnValues,
boolean writePropertyValue,
boolean allowMutation)
implements OnExecutionGenerator {
implements CompositeOnExecutionGenerator {
@Override
public boolean referenceColumnsInSql(Dialect dialect) {
return referenceColumnsInSql;
@ -158,6 +163,11 @@ class CompositeGeneratorBuilder {
public EnumSet<EventType> getEventTypes() {
return eventTypes;
}
@Override
public OnExecutionGenerator getPropertyGenerator(String propertyName) {
return generatedPropertiesByName.get( propertyName );
}
}
private record CompositeBeforeExecutionGenerator(

View File

@ -27,6 +27,7 @@ import org.hibernate.engine.spi.CascadeStyle;
import org.hibernate.engine.spi.CascadeStyles;
import org.hibernate.engine.spi.CascadingActions;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.generator.CompositeOnExecutionGenerator;
import org.hibernate.generator.Generator;
import org.hibernate.generator.OnExecutionGenerator;
import org.hibernate.generator.BeforeExecutionGenerator;
@ -328,7 +329,7 @@ public class EntityMetamodel implements Serializable {
propertyCheckability[i] = false;
}
if ( generator.generatesOnInsert() ) {
if ( generatedOnExecution ) {
if ( generatedOnExecution && !(generator instanceof CompositeOnExecutionGenerator) ) {
propertyInsertability[i] = writePropertyValue( (OnExecutionGenerator) generator );
}
foundPostInsertGeneratedValues = foundPostInsertGeneratedValues
@ -340,7 +341,7 @@ public class EntityMetamodel implements Serializable {
propertyInsertability[i] = false;
}
if ( generator.generatesOnUpdate() ) {
if ( generatedOnExecution ) {
if ( generatedOnExecution && !(generator instanceof CompositeOnExecutionGenerator) ) {
propertyUpdateability[i] = writePropertyValue( (OnExecutionGenerator) generator );
}
foundPostUpdateGeneratedValues = foundPostUpdateGeneratedValues
@ -529,20 +530,25 @@ public class EntityMetamodel implements Serializable {
final Property mappingProperty,
final RuntimeModelCreationContext context) {
final GeneratorCreator generatorCreator = mappingProperty.getValueGeneratorCreator();
if ( mappingProperty.getValue() instanceof Component component ) {
final CompositeGeneratorBuilder builder =
new CompositeGeneratorBuilder( entityName, mappingProperty, context.getDialect() );
for ( Property property : component.getProperties() ) {
if ( property.getValue() instanceof Component ) {
builder.add( property.getName(), buildGenerator( entityName, property, context ) );
}
else {
builder.add( property.getName(), property.createGenerator( context ) );
}
}
return builder.build();
}
if ( generatorCreator != null ) {
final Generator generator = mappingProperty.createGenerator( context );
if ( generator.generatesSometimes() ) {
return generator;
}
}
if ( mappingProperty.getValue() instanceof Component component ) {
final CompositeGeneratorBuilder builder =
new CompositeGeneratorBuilder( entityName, mappingProperty, context.getDialect() );
for ( Property property : component.getProperties() ) {
builder.add( property.createGenerator( context ) );
}
return builder.build();
}
return null;
}