diff --git a/hibernate-core/src/main/java/org/hibernate/generator/CompositeBeforeExecutionGenerator.java b/hibernate-core/src/main/java/org/hibernate/generator/CompositeBeforeExecutionGenerator.java new file mode 100644 index 000000000000..037607c44527 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/generator/CompositeBeforeExecutionGenerator.java @@ -0,0 +1,10 @@ +/* + * 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.generator; + +public interface CompositeBeforeExecutionGenerator extends BeforeExecutionGenerator, CompositeGenerator { +} diff --git a/hibernate-core/src/main/java/org/hibernate/generator/CompositeGenerator.java b/hibernate-core/src/main/java/org/hibernate/generator/CompositeGenerator.java new file mode 100644 index 000000000000..5a3dc22ed41d --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/generator/CompositeGenerator.java @@ -0,0 +1,11 @@ +/* + * 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.generator; + +public interface CompositeGenerator extends Generator { + Generator getGenerator(int attributeStateArrayPosition); +} diff --git a/hibernate-core/src/main/java/org/hibernate/generator/internal/OnExecutionCompositeGenerator.java b/hibernate-core/src/main/java/org/hibernate/generator/internal/OnExecutionCompositeGenerator.java new file mode 100644 index 000000000000..4b296c10e73c --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/generator/internal/OnExecutionCompositeGenerator.java @@ -0,0 +1,68 @@ +/* + * 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.generator.internal; + +import java.util.EnumSet; +import java.util.List; +import java.util.Map; + +import org.hibernate.dialect.Dialect; +import org.hibernate.generator.CompositeGenerator; +import org.hibernate.generator.EventType; +import org.hibernate.generator.Generator; +import org.hibernate.generator.OnExecutionGenerator; + +public class OnExecutionCompositeGenerator implements OnExecutionGenerator, CompositeGenerator { + + final List generators; + final EnumSet eventTypes; + final boolean writePropertyValue; + final boolean referenceColumnsInSql; + final String[] referencedColumnValues; + + public OnExecutionCompositeGenerator( + List generators, + EnumSet eventTypes, + boolean writePropertyValue, + boolean referenceColumnsInSql, String[] referencedColumnValues) { + this.generators = generators; + this.eventTypes = eventTypes; + this.writePropertyValue = writePropertyValue; + this.referenceColumnsInSql = referenceColumnsInSql; + this.referencedColumnValues = referencedColumnValues; + } + + @Override + public EnumSet getEventTypes() { + return eventTypes; + } + + @Override + public boolean referenceColumnsInSql(Dialect dialect) { + return referenceColumnsInSql; + } + + @Override + public boolean writePropertyValue() { + return writePropertyValue; + } + + @Override + public String[] getReferencedColumnValues(Dialect dialect) { + return referencedColumnValues; + } + + @Override + public boolean generatedOnExecution() { + return true; + } + + @Override + public Generator getGenerator(int attributeStateArrayPosition) { + return generators.get( attributeStateArrayPosition ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicAttributeMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicAttributeMapping.java index 1e7d61dc9bc5..04ee1aef0cc0 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicAttributeMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicAttributeMapping.java @@ -43,7 +43,7 @@ @SuppressWarnings("rawtypes") public class BasicAttributeMapping extends AbstractSingularAttributeMapping - implements SingularAttributeMapping, BasicValuedModelPart { + implements BasicValuedModelPart { private final NavigableRole navigableRole; private final String tableExpression; diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddableMappingTypeImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddableMappingTypeImpl.java index a5ac378070bf..38bcfbc5cd2d 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddableMappingTypeImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddableMappingTypeImpl.java @@ -27,6 +27,9 @@ import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.CascadeStyle; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.generator.Generator; +import org.hibernate.generator.OnExecutionGenerator; +import org.hibernate.generator.internal.OnExecutionCompositeGenerator; import org.hibernate.internal.util.config.ConfigurationHelper; import org.hibernate.mapping.AggregateColumn; import org.hibernate.mapping.Any; @@ -48,6 +51,7 @@ import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.JdbcMapping; +import org.hibernate.metamodel.mapping.ManagedMappingType; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.SelectableConsumer; import org.hibernate.metamodel.mapping.SelectableMapping; @@ -57,6 +61,7 @@ import org.hibernate.metamodel.spi.EmbeddableInstantiator; import org.hibernate.metamodel.spi.EmbeddableRepresentationStrategy; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; +import org.hibernate.persister.entity.AbstractEntityPersister; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.property.access.spi.Getter; import org.hibernate.property.access.spi.PropertyAccess; @@ -183,6 +188,8 @@ public static EmbeddableMappingTypeImpl from( private final boolean preferSelectAggregateMapping; private final boolean preferBindAggregateMapping; + private final OnExecutionCompositeGenerator onExecutionCompositeGenerator; + private EmbeddableMappingTypeImpl( Component bootDescriptor, Property componentProperty, @@ -196,6 +203,7 @@ private EmbeddableMappingTypeImpl( this.embeddableJtd = representationStrategy.getMappedJavaType(); this.valueMapping = embeddedPartBuilder.apply( this ); + onExecutionCompositeGenerator = getOnExecutionCompositeGenerator( valueMapping ); this.discriminatorMapping = generateDiscriminatorMapping( bootDescriptor, creationContext ); if ( bootDescriptor.isPolymorphic() ) { this.concreteEmbeddableByDiscriminator = new HashMap<>(); @@ -269,6 +277,86 @@ private EmbeddableMappingTypeImpl( } } + public EmbeddableMappingTypeImpl( + EmbeddedAttributeMapping valueMapping, + TableGroupProducer declaringTableGroupProducer, + SelectableMappings selectableMappings, + EmbeddableMappingType inverseMappingType, + MappingModelCreationProcess creationProcess) { + super( new MutableAttributeMappingList( 5 ) ); + + this.embeddableJtd = inverseMappingType.getJavaType(); + this.representationStrategy = inverseMappingType.getRepresentationStrategy(); + this.valueMapping = valueMapping; + onExecutionCompositeGenerator = getOnExecutionCompositeGenerator( valueMapping ); + this.discriminatorMapping = null; + this.concreteEmbeddableBySubclass = null; + this.concreteEmbeddableByDiscriminator = null; + this.createEmptyCompositesEnabled = inverseMappingType.isCreateEmptyCompositesEnabled(); + this.aggregateMapping = null; + this.aggregateMappingRequiresColumnWriter = false; + this.preferSelectAggregateMapping = false; + this.preferBindAggregateMapping = false; + this.selectableMappings = selectableMappings; + creationProcess.registerInitializationCallback( + "EmbeddableMappingType(" + inverseMappingType.getNavigableRole().getFullPath() + ".{inverse})#finishInitialization", + () -> inverseInitializeCallback( + declaringTableGroupProducer, + selectableMappings, + inverseMappingType, + creationProcess, + this, + attributeMappings + ) + ); + } + + public EmbeddableMappingType createInverseMappingType( + EmbeddedAttributeMapping valueMapping, + TableGroupProducer declaringTableGroupProducer, + SelectableMappings selectableMappings, + MappingModelCreationProcess creationProcess) { + return new EmbeddableMappingTypeImpl( + valueMapping, + declaringTableGroupProducer, + selectableMappings, + this, + creationProcess + ); + } + + private static OnExecutionCompositeGenerator getOnExecutionCompositeGenerator(EmbeddableValuedModelPart valueMapping) { + if ( valueMapping instanceof EmbeddedAttributeMapping ) { + final EmbeddedAttributeMapping embeddedAttributeMapping = (EmbeddedAttributeMapping) valueMapping; + final int stateArrayPosition = embeddedAttributeMapping.getStateArrayPosition(); + if ( stateArrayPosition == -1 ) { + return null; + } + final Generator generator; + ManagedMappingType declaringType = ( (EmbeddedAttributeMapping) valueMapping ).getDeclaringType(); + if ( declaringType instanceof AbstractEntityPersister ) { + generator = valueMapping.findContainingEntityMapping().getEntityPersister() + .getEntityMetamodel() + .getGenerators()[stateArrayPosition]; + } + else if ( declaringType instanceof EmbeddableMappingTypeImpl ) { + generator = ( (EmbeddableMappingTypeImpl) declaringType ).getOnExecutionCompositeGenerator(); + } + else { + return null; + } + if ( generator instanceof OnExecutionCompositeGenerator ) { + return (OnExecutionCompositeGenerator) generator; + } + else { + return null; + } + } + else { + return null; + } + } + private JdbcMapping resolveJdbcMapping(Component bootDescriptor, RuntimeModelCreationContext creationContext) { // The following is a bit "hacky" because ideally, this should happen in InferredBasicValueResolver#from, // but since we don't have access to the EmbeddableMappingType there yet, we do it here. @@ -350,53 +438,6 @@ private JdbcMapping resolveJdbcMapping(Component bootDescriptor, RuntimeModelCre return resolvedJdbcMapping; } - public EmbeddableMappingTypeImpl( - EmbeddedAttributeMapping valueMapping, - TableGroupProducer declaringTableGroupProducer, - SelectableMappings selectableMappings, - EmbeddableMappingType inverseMappingType, - MappingModelCreationProcess creationProcess) { - super( new MutableAttributeMappingList( 5 ) ); - - this.embeddableJtd = inverseMappingType.getJavaType(); - this.representationStrategy = inverseMappingType.getRepresentationStrategy(); - this.valueMapping = valueMapping; - this.discriminatorMapping = null; - this.concreteEmbeddableBySubclass = null; - this.concreteEmbeddableByDiscriminator = null; - this.createEmptyCompositesEnabled = inverseMappingType.isCreateEmptyCompositesEnabled(); - this.aggregateMapping = null; - this.aggregateMappingRequiresColumnWriter = false; - this.preferSelectAggregateMapping = false; - this.preferBindAggregateMapping = false; - this.selectableMappings = selectableMappings; - creationProcess.registerInitializationCallback( - "EmbeddableMappingType(" + inverseMappingType.getNavigableRole().getFullPath() + ".{inverse})#finishInitialization", - () -> inverseInitializeCallback( - declaringTableGroupProducer, - selectableMappings, - inverseMappingType, - creationProcess, - this, - attributeMappings - ) - ); - } - - public EmbeddableMappingType createInverseMappingType( - EmbeddedAttributeMapping valueMapping, - TableGroupProducer declaringTableGroupProducer, - SelectableMappings selectableMappings, - MappingModelCreationProcess creationProcess) { - return new EmbeddableMappingTypeImpl( - valueMapping, - declaringTableGroupProducer, - selectableMappings, - this, - creationProcess - ); - } - private boolean finishInitialization( Component bootDescriptor, CompositeType compositeType, @@ -433,8 +474,9 @@ private boolean finishInitialization( // ); // todo (6.0) - get this ^^ to work, or drop the comment - final TypeConfiguration typeConfiguration = creationProcess.getCreationContext().getTypeConfiguration(); - final JdbcServices jdbcServices = creationProcess.getCreationContext().getJdbcServices(); + final RuntimeModelCreationContext creationContext = creationProcess.getCreationContext(); + final TypeConfiguration typeConfiguration = creationContext.getTypeConfiguration(); + final JdbcServices jdbcServices = creationContext.getJdbcServices(); final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment(); final Dialect dialect = jdbcEnvironment.getDialect(); @@ -447,12 +489,32 @@ private boolean finishInitialization( // 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 OnExecutionGenerator onExecutionGenerator; + if ( onExecutionCompositeGenerator != null ) { + onExecutionGenerator = (OnExecutionGenerator) onExecutionCompositeGenerator + .getGenerator( attributeIndex ); + } + else { + onExecutionGenerator = null; + } + if ( subtype instanceof BasicType ) { + final boolean insertable; + final boolean updateable; + if ( generatedWithNoParameter( onExecutionGenerator ) ) { + insertable = false; + updateable = false; + } + else { + insertable = insertability[columnPosition]; + updateable = updateability[columnPosition]; + } final BasicValue basicValue = (BasicValue) value; final Selectable selectable = dependantValue != null ? dependantValue.getColumns().get( dependantColumnIndex + columnPosition ) : @@ -463,7 +525,7 @@ private boolean finishInitialization( if ( selectable.isFormula() ) { columnExpression = selectable.getTemplate( dialect, - creationProcess.getCreationContext().getTypeConfiguration(), + creationContext.getTypeConfiguration(), creationProcess.getSqmFunctionRegistry() ); } @@ -501,7 +563,7 @@ private boolean finishInitialization( precision = column.getPrecision(); scale = column.getScale(); temporalPrecision = column.getTemporalPrecision(); - isLob = column.isSqlTypeLob( creationProcess.getCreationContext().getMetadata() ); + isLob = column.isSqlTypeLob( creationContext.getMetadata() ); nullable = bootPropertyDescriptor.isOptional() && column.isNullable() ; selectablePath = basicValue.createSelectablePath( column.getQuotedName( dialect ) ); MappingModelCreationHelper.resolveAggregateColumnBasicType( creationProcess, role, column ); @@ -537,8 +599,8 @@ private boolean finishInitialization( temporalPrecision, isLob, nullable, - insertability[columnPosition], - updateability[columnPosition], + insertable, + updateable, representationStrategy.resolvePropertyAccess( bootPropertyDescriptor ), compositeType.getCascadeStyle( attributeIndex ), creationProcess @@ -552,11 +614,19 @@ else if ( subtype instanceof AnyType ) { final PropertyAccess propertyAccess = representationStrategy.resolvePropertyAccess( bootPropertyDescriptor ); final boolean nullable = bootValueMapping.isNullable(); - final boolean insertable = insertability[columnPosition]; - final boolean updateable = updateability[columnPosition]; final boolean includeInOptimisticLocking = bootPropertyDescriptor.isOptimisticLocked(); final CascadeStyle cascadeStyle = compositeType.getCascadeStyle( attributeIndex ); + final boolean insertable; + final boolean updateable; + if ( generatedWithNoParameter( onExecutionGenerator ) ) { + insertable = false; + updateable = false; + } + else { + insertable = insertability[columnPosition]; + updateable = updateability[columnPosition]; + } SimpleAttributeMetadata attributeMetadataAccess = new SimpleAttributeMetadata( propertyAccess, getMutabilityPlan( updateable ), @@ -585,7 +655,7 @@ else if ( subtype instanceof AnyType ) { } else if ( subtype instanceof CompositeType ) { final CompositeType subCompositeType = (CompositeType) subtype; - final int columnSpan = subCompositeType.getColumnSpan( creationProcess.getCreationContext().getMetadata() ); + final int columnSpan = subCompositeType.getColumnSpan( creationContext.getMetadata() ); final String subTableExpression; final String[] subRootTableKeyColumnNames; if ( rootTableKeyColumnNames == null ) { @@ -1139,4 +1209,15 @@ public boolean shouldSelectAggregateMapping() { public boolean shouldBindAggregateMapping() { return preferBindAggregateMapping; } + + public OnExecutionCompositeGenerator getOnExecutionCompositeGenerator() { + return onExecutionCompositeGenerator; + } + + private static boolean generatedWithNoParameter(Generator generator) { + return generator != null + && generator.generatedOnExecution() + && !( (OnExecutionGenerator) generator ).writePropertyValue(); + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationHelper.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationHelper.java index 0d9d96459aab..555a1f4fd9c6 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationHelper.java @@ -1361,7 +1361,7 @@ private static CollectionPart interpretMapKey( if ( bootMapKeyDescriptor instanceof Component ) { final Component component = (Component) bootMapKeyDescriptor; - final CompositeType compositeType = (CompositeType) component.getType(); + final CompositeType compositeType = component.getType(); final EmbeddableMappingTypeImpl mappingType = EmbeddableMappingTypeImpl.from( diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/AbstractMutationCoordinator.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/AbstractMutationCoordinator.java index 971697403c66..8a41dc73f56f 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/AbstractMutationCoordinator.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/AbstractMutationCoordinator.java @@ -136,6 +136,30 @@ protected void handleValueGeneration( } ); } + protected void handleUpdateValueGeneration( + AttributeMapping attributeMapping, + MutationGroupBuilder mutationGroupBuilder, + UpdateCoordinatorStandard.DirtinessChecker dirtinessChecker, + 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 ); + if ( !entityPersister().getEntityMetamodel().isDynamicUpdate() + || dirtinessChecker == null + || dirtinessChecker.isDirty( j, attributeMapping ).isDirty() ) { + tableUpdateBuilder.addValueColumn( + mapping.getSelectionExpression(), + writePropertyValue ? "?" : columnValues[j], + mapping.getJdbcMapping(), + mapping.isLob() + ); + } + } ); + } + protected void bindPartitionColumnValueBindings( Object[] loadedState, SharedSessionContractImplementor session, diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/InsertCoordinatorStandard.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/InsertCoordinatorStandard.java index 6b74d8593c59..5b0644724c2f 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/InsertCoordinatorStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/InsertCoordinatorStandard.java @@ -20,6 +20,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.generator.BeforeExecutionGenerator; +import org.hibernate.generator.CompositeGenerator; import org.hibernate.generator.Generator; import org.hibernate.generator.OnExecutionGenerator; import org.hibernate.generator.values.GeneratedValues; @@ -222,7 +223,7 @@ protected void decomposeForInsert( SharedSessionContractImplementor session) { final JdbcValueBindings jdbcValueBindings = mutationExecutor.getJdbcValueBindings(); final AttributeMappingsList attributeMappings = entityPersister().getAttributeMappings(); - + final boolean isDynamicInsert = entityPersister().getEntityMetamodel().isDynamicInsert(); for ( int position = 0; position < mutationGroup.getNumberOfOperations(); position++ ) { final MutationOperation operation = mutationGroup.getOperation( position ); final EntityTableMapping tableDetails = (EntityTableMapping) operation.getTableDetails(); @@ -230,9 +231,31 @@ protected void decomposeForInsert( final int[] attributeIndexes = tableDetails.getAttributeIndexes(); for ( int i = 0; i < attributeIndexes.length; i++ ) { final int attributeIndex = attributeIndexes[ i ]; - if ( propertyInclusions[attributeIndex] ) { + final Object value = values[attributeIndex]; + if ( propertyInclusions[attributeIndex] && !( isDynamicInsert && value == null ) ) { final AttributeMapping mapping = attributeMappings.get( attributeIndex ); - decomposeAttribute( values[attributeIndex], session, jdbcValueBindings, mapping ); + if ( mapping.isEmbeddedAttributeMapping() ) { + mapping.decompose( + value, + 0, + jdbcValueBindings, + null, + (valueIndex, bindings, noop, jdbcValue, selectableMapping) -> { + if ( selectableMapping.isInsertable() && !( isDynamicInsert && jdbcValue == null ) ) { + bindings.bindValue( + jdbcValue, + entityPersister().physicalTableNameForMutation( selectableMapping ), + selectableMapping.getSelectionExpression(), + ParameterUsage.SET + ); + } + }, + session + ); + } + else { + decomposeAttribute( value, session, jdbcValueBindings, mapping ); + } } } } @@ -303,7 +326,7 @@ protected GeneratedValues doDynamicInserts( SharedSessionContractImplementor session, boolean forceIdentifierBinding) { final boolean[] insertability = getPropertiesToInsert( values ); - final MutationOperationGroup insertGroup = generateDynamicInsertSqlGroup( insertability, object, session, forceIdentifierBinding ); + final MutationOperationGroup insertGroup = generateDynamicInsertSqlGroup( insertability, values, object, session, forceIdentifierBinding ); final MutationExecutor mutationExecutor = executor( session, insertGroup, true ); @@ -360,14 +383,14 @@ public boolean[] getPropertiesToInsert(Object[] fields) { protected MutationOperationGroup generateDynamicInsertSqlGroup( boolean[] insertable, + Object[] values, Object object, SharedSessionContractImplementor session, boolean forceIdentifierBinding) { final MutationGroupBuilder insertGroupBuilder = new MutationGroupBuilder( MutationType.INSERT, entityPersister() ); entityPersister().forEachMutableTable( - (tableMapping) -> insertGroupBuilder.addTableDetailsBuilder( createTableInsertBuilder( tableMapping, forceIdentifierBinding ) ) - ); - applyTableInsertDetails( insertGroupBuilder, insertable, object, session, forceIdentifierBinding ); + (tableMapping) -> insertGroupBuilder.addTableDetailsBuilder( createTableInsertBuilder( tableMapping, forceIdentifierBinding ) ) ); + applyDynamicTableInsertDetails( insertGroupBuilder, insertable, values, object, session, forceIdentifierBinding ); return createOperationGroup( null, insertGroupBuilder.buildMutationGroup() ); } @@ -397,7 +420,6 @@ private void applyTableInsertDetails( SharedSessionContractImplementor session, boolean forceIdentifierBinding) { final AttributeMappingsList attributeMappings = entityPersister().getAttributeMappings(); - insertGroupBuilder.forEachTableMutationBuilder( (builder) -> { final EntityTableMapping tableMapping = (EntityTableMapping) builder.getMutatingTable().getTableMapping(); assert !tableMapping.isInverse(); @@ -409,7 +431,36 @@ private void applyTableInsertDetails( final int attributeIndex = attributeIndexes[ i ]; final AttributeMapping attributeMapping = attributeMappings.get( attributeIndex ); if ( attributeInclusions[attributeIndex] ) { - attributeMapping.forEachInsertable( insertGroupBuilder ); + if ( attributeMapping.isEmbeddedAttributeMapping() ) { + final CompositeGenerator compositeGenerator = (CompositeGenerator) attributeMapping.getGenerator(); + attributeMapping.forEachInsertable( + (selectionIndex, selectableMapping) -> { + if ( selectableMapping instanceof AttributeMapping ) { + final AttributeMapping mapping = (AttributeMapping) selectableMapping; + final Generator generator = compositeGenerator == null ? + null : + compositeGenerator.getGenerator( mapping.getStateArrayPosition() ); + if ( isValueGenerated( generator ) ) { + if ( session != null && !generator.generatedOnExecution( object, session ) ) { + mapping.forEachInsertable( insertGroupBuilder ); + } + else if ( isValueGenerationInSql( generator, factory().getJdbcServices().getDialect() ) ) { + handleValueGeneration( mapping, insertGroupBuilder, (OnExecutionGenerator) generator ); + } + } + else { + mapping.forEachInsertable( insertGroupBuilder ); + } + } + else { + insertGroupBuilder.accept( selectionIndex, selectableMapping ); + } + } + ); + } + else { + attributeMapping.forEachInsertable( insertGroupBuilder ); + } } else { final Generator generator = attributeMapping.getGenerator(); @@ -453,6 +504,112 @@ else if ( isValueGenerationInSql( generator, factory().getJdbcServices().getDial } ); } + private void applyDynamicTableInsertDetails( + MutationGroupBuilder insertGroupBuilder, + boolean[] attributeInclusions, + Object[] values, + Object object, + SharedSessionContractImplementor session, + boolean forceIdentifierBinding) { + final AttributeMappingsList attributeMappings = entityPersister().getAttributeMappings(); + + insertGroupBuilder.forEachTableMutationBuilder( (builder) -> { + final EntityTableMapping tableMapping = (EntityTableMapping) builder.getMutatingTable().getTableMapping(); + assert !tableMapping.isInverse(); + + // `attributeIndexes` represents the indexes (relative to `attributeMappings`) of + // the attributes mapped to the table + final int[] attributeIndexes = tableMapping.getAttributeIndexes(); + for ( int i = 0; i < attributeIndexes.length; i++ ) { + final int attributeIndex = attributeIndexes[ i ]; + final AttributeMapping attributeMapping = attributeMappings.get( attributeIndex ); + final Object attributeValue = values[attributeIndex] ; + if ( attributeInclusions[attributeIndex] ) { + if ( attributeMapping.isEmbeddedAttributeMapping() ) { + CompositeGenerator compositeGenerator = (CompositeGenerator) attributeMapping.getGenerator(); + attributeMapping.forEachInsertable( + (selectionIndex, selectableMapping) -> { + if ( selectableMapping instanceof AttributeMapping ) { + final AttributeMapping mapping = (AttributeMapping) selectableMapping; + final Object value = mapping.getValue( attributeValue ); + final Generator generator = compositeGenerator == null ? + null : + compositeGenerator.getGenerator( mapping.getStateArrayPosition() ); + if ( isValueGenerated( generator ) ) { + if ( session != null && !generator.generatedOnExecution( + object, + session + ) ) { + mapping.forEachInsertable( insertGroupBuilder ); + } + else if ( value != null + && isValueGenerationInSql( + generator, + factory().getJdbcServices().getDialect() + ) ) { + handleValueGeneration( + mapping, + insertGroupBuilder, + (OnExecutionGenerator) generator + ); + } + } + else if ( value != null ) { + mapping.forEachInsertable( insertGroupBuilder ); + } + } + else if ( attributeValue != null ) { + insertGroupBuilder.accept( selectionIndex, selectableMapping ); + } + } + ); + } + else { + attributeMapping.forEachInsertable( insertGroupBuilder ); + } + } + else { + final Generator generator = attributeMapping.getGenerator(); + if ( isValueGenerated( generator ) ) { + if ( session != null && !generator.generatedOnExecution( object, session ) ) { + attributeInclusions[attributeIndex] = true; + attributeMapping.forEachInsertable( insertGroupBuilder ); + } + else if ( attributeValue != null && isValueGenerationInSql( generator, factory().getJdbcServices().getDialect() ) ) { + handleValueGeneration( attributeMapping, insertGroupBuilder, (OnExecutionGenerator) generator ); + } + } + } + } + } ); + + // add the discriminator + entityPersister().addDiscriminatorToInsertGroup( insertGroupBuilder ); + entityPersister().addSoftDeleteToInsertGroup( insertGroupBuilder ); + + // add the keys + insertGroupBuilder.forEachTableMutationBuilder( (tableMutationBuilder) -> { + final TableInsertBuilder tableInsertBuilder = (TableInsertBuilder) tableMutationBuilder; + final EntityTableMapping tableMapping = (EntityTableMapping) tableInsertBuilder.getMutatingTable().getTableMapping(); + if ( tableMapping.isIdentifierTable() && entityPersister().isIdentifierAssignedByInsert() && !forceIdentifierBinding ) { + assert entityPersister().getInsertDelegate() != null; + final OnExecutionGenerator generator = (OnExecutionGenerator) entityPersister().getGenerator(); + if ( generator.referenceColumnsInSql( dialect() ) ) { + final BasicEntityIdentifierMapping identifierMapping = (BasicEntityIdentifierMapping) entityPersister().getIdentifierMapping(); + final String[] columnValues = generator.getReferencedColumnValues( dialect ); + tableMapping.getKeyMapping().forEachKeyColumn( (i, column) -> tableInsertBuilder.addKeyColumn( + column.getColumnName(), + columnValues[i], + identifierMapping.getJdbcMapping() + ) ); + } + } + else { + tableMapping.getKeyMapping().forEachKeyColumn( tableInsertBuilder::addKeyColumn ); + } + } ); + } + private static boolean isValueGenerated(Generator generator) { return generator != null && generator.generatesOnInsert() diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/UpdateCoordinatorStandard.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/UpdateCoordinatorStandard.java index 5df81f6a51c5..edfe2b5e6219 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/UpdateCoordinatorStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/UpdateCoordinatorStandard.java @@ -28,6 +28,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.generator.BeforeExecutionGenerator; +import org.hibernate.generator.CompositeGenerator; import org.hibernate.generator.Generator; import org.hibernate.generator.OnExecutionGenerator; import org.hibernate.generator.values.GeneratedValues; @@ -40,6 +41,8 @@ import org.hibernate.metamodel.mapping.EntityVersionMapping; import org.hibernate.metamodel.mapping.SelectableMapping; import org.hibernate.metamodel.mapping.SingularAttributeMapping; +import org.hibernate.metamodel.mapping.internal.BasicAttributeMapping; +import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping; import org.hibernate.persister.entity.AbstractEntityPersister; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.sql.model.MutationOperation; @@ -850,11 +853,31 @@ private void decomposeAttributeForUpdate( final AttributeAnalysis attributeAnalysisRef = valuesAnalysis.attributeAnalyses.get( attributeIndex ); if ( !attributeAnalysisRef.isSkipped() ) { final IncludedAttributeAnalysis attributeAnalysis = (IncludedAttributeAnalysis) attributeAnalysisRef; - if ( attributeAnalysis.includeInSet() ) { // apply the new values - if ( includeInSet( dirtinessChecker, attributeIndex, attributeMapping, attributeAnalysis ) ) { - decomposeAttributeMapping(session, jdbcValueBindings, tableMapping, attributeMapping, values[attributeIndex] ); + if ( attributeMapping.isEmbeddedAttributeMapping() ) { + decomposeEmbeddedAttributeMapping( + values[attributeIndex], + session, + jdbcValueBindings, + tableMapping, + entityPersister().getEntityMetamodel().isDynamicUpdate() && dirtinessChecker != null, + dirtinessChecker != null ? + dirtinessChecker.isDirty( attributeIndex, attributeMapping ).isDirty() : + true, + attributeMapping + ); + } + else { + if ( includeInSet( dirtinessChecker, attributeIndex, attributeMapping, attributeAnalysis ) ) { + decomposeAttributeMapping( + session, + jdbcValueBindings, + tableMapping, + attributeMapping, + values[attributeIndex] + ); + } } } @@ -866,6 +889,84 @@ private void decomposeAttributeForUpdate( } } + private void decomposeEmbeddedAttributeMapping( + Object value, + SharedSessionContractImplementor session, + JdbcValueBindings jdbcValueBindings, + EntityTableMapping tableMapping, + boolean isDynamicUpdate, + boolean isDirty, + AttributeMapping attributeMapping) { + final CompositeGenerator compositeGenerator = (CompositeGenerator) attributeMapping.getGenerator(); + attributeMapping.decompose( + value, + 0, + jdbcValueBindings, + tableMapping, + (valueIndex, bindings, table, jdbcValue, jdbcMapping) -> { + if ( jdbcMapping instanceof EmbeddedAttributeMapping ) { + decomposeEmbeddedAttributeMapping( + jdbcValue, + session, + jdbcValueBindings, + tableMapping, + isDynamicUpdate, + isDirty, + (AttributeMapping) jdbcMapping + ); + } + else if ( !isDynamicUpdate || ( isDynamicUpdate && isDirty ) ) { + if ( jdbcMapping instanceof SingularAttributeMapping ) { + final SingularAttributeMapping singularAttributeMapping = (SingularAttributeMapping) jdbcMapping; + final Generator generator = compositeGenerator == null ? + null : + compositeGenerator.getGenerator( singularAttributeMapping.getStateArrayPosition() ); + final boolean valueGenerated = isValueGenerated( generator ); + if ( valueGenerated ) { + if ( isValueGenerationInSql( generator, dialect ) + && ( (OnExecutionGenerator) generator ).writePropertyValue() ) { + bindings.bindValue( + jdbcValue, + table.getTableName(), + jdbcMapping.getSelectionExpression(), + ParameterUsage.SET + ); + } + else if ( !jdbcMapping.isFormula() && jdbcMapping.isUpdateable() ) { + bindings.bindValue( + jdbcValue, + table.getTableName(), + jdbcMapping.getSelectionExpression(), + ParameterUsage.SET + ); + } + } + else { + decomposeAttributeMapping( + session, + bindings, + tableMapping, + singularAttributeMapping, + jdbcValue + ); + } + } + else { + if ( !jdbcMapping.isFormula() && jdbcMapping.isUpdateable() ) { + bindings.bindValue( + jdbcValue, + table.getTableName(), + jdbcMapping.getSelectionExpression(), + ParameterUsage.SET + ); + } + } + } + }, + session + ); + } + private static void optimisticLock( SharedSessionContractImplementor session, JdbcValueBindings jdbcValueBindings, @@ -1238,10 +1339,42 @@ private void applyAttributeUpdateDetails( TableUpdateBuilder tableUpdateBuilder, SharedSessionContractImplementor session) { final Generator generator = attributeMapping.getGenerator(); - if ( isValueGenerated( generator ) - && ( session == null && generator.generatedOnExecution() || generator.generatedOnExecution( entity, session ) ) - && isValueGenerationInSql( generator, dialect ) ) { - handleValueGeneration( attributeMapping, updateGroupBuilder, (OnExecutionGenerator) generator ); + if ( attributeMapping.isEmbeddedAttributeMapping() ) { + final CompositeGenerator compositeGenerator = (CompositeGenerator) generator; + attributeMapping.forEachUpdatable( + (index, selectable) -> { + Generator gen = null; + if ( compositeGenerator != null && selectable instanceof AttributeMapping ) { + gen = compositeGenerator.getGenerator( ( (AttributeMapping) selectable ).getStateArrayPosition() ); + } + if ( handleGeneratedValue( entity, session, gen ) ) { + handleUpdateValueGeneration( + (AttributeMapping) selectable, + updateGroupBuilder, + dirtinessChecker, + (OnExecutionGenerator) gen + ); + } + } + ); + // todo: this code is ugly, I think it can ve included in the the previous foreach + final boolean includeInSet = !entityPersister().getEntityMetamodel().isDynamicUpdate() + || dirtinessChecker == null + || dirtinessChecker.isDirty( attributeIndex, attributeMapping ).isDirty(); + if ( includeInSet ) { + attributeMapping.forEachUpdatable( (index, selectable) -> { + Generator gen = null; + if ( compositeGenerator != null && selectable instanceof AttributeMapping ) { + gen = compositeGenerator.getGenerator( ( (AttributeMapping) selectable ).getStateArrayPosition() ); + } + if ( !handleGeneratedValue( entity, session, gen ) ) { + tableUpdateBuilder.addValueColumn( selectable ); + } + } ); + } + } + else if ( handleGeneratedValue( entity, session, generator ) ) { + handleUpdateValueGeneration( attributeMapping, updateGroupBuilder, dirtinessChecker, (OnExecutionGenerator) generator ); } else if ( versionMapping != null && versionMapping.getVersionAttribute() == attributeMapping) { @@ -1257,6 +1390,12 @@ else if ( versionMapping != null } } + private boolean handleGeneratedValue(Object entity, SharedSessionContractImplementor session, Generator gen) { + return isValueGenerated( gen ) + && ( session == null && gen.generatedOnExecution() || gen.generatedOnExecution( entity, session ) ) + && isValueGenerationInSql( gen, dialect ); + } + /** * Contains the aggregated analysis of the update values to determine * what SQL UPDATE statement(s) should be used to update the entity diff --git a/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcParametersList.java b/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcParametersList.java index 3d9e94b4246c..5a4109823bb6 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcParametersList.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcParametersList.java @@ -18,17 +18,17 @@ */ public interface JdbcParametersList { - static final JdbcParametersList EMPTY = new JdbcParametersListMulti( new JdbcParameter[]{} ); + JdbcParametersList EMPTY = new JdbcParametersListMulti( new JdbcParameter[]{} ); - public JdbcParameter get(int selectionIndex); + JdbcParameter get(int selectionIndex); - public int size(); + int size(); - public static Builder newBuilder() { + static Builder newBuilder() { return newBuilder( 2 ); } - public static JdbcParametersList fromList(final List originalList) { + static JdbcParametersList fromList(final List originalList) { final Builder builder = newBuilder( originalList.size() ); for ( JdbcParameter element : originalList ) { builder.add( element ); @@ -36,19 +36,19 @@ public static JdbcParametersList fromList(final List originalList return builder.build(); } - public static JdbcParametersList empty() { + static JdbcParametersList empty() { return EMPTY; } - public static JdbcParametersList singleton(final JdbcParameter p) { + static JdbcParametersList singleton(final JdbcParameter p) { return new JdbcParametersListSingleton( p ); } - public static Builder newBuilder(final int i) { + static Builder newBuilder(final int i) { return new Builder( i ); } - public static class Builder { + class Builder { private JdbcParameter[] array; private int index = 0; @@ -84,7 +84,7 @@ else if ( index == array.length ) { } } - public final class JdbcParametersListMulti implements JdbcParametersList { + final class JdbcParametersListMulti implements JdbcParametersList { private final JdbcParameter[] array; @@ -101,7 +101,7 @@ public int size() { } } - public final class JdbcParametersListSingleton implements JdbcParametersList { + final class JdbcParametersListSingleton implements JdbcParametersList { private final JdbcParameter singleElement; diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/CompositeGeneratorBuilder.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/CompositeGeneratorBuilder.java index aa507b68e86d..937fb85df787 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/CompositeGeneratorBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/CompositeGeneratorBuilder.java @@ -9,9 +9,12 @@ import org.hibernate.dialect.Dialect; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.generator.BeforeExecutionGenerator; +import org.hibernate.generator.CompositeBeforeExecutionGenerator; +import org.hibernate.generator.CompositeGenerator; import org.hibernate.generator.EventType; import org.hibernate.generator.Generator; import org.hibernate.generator.OnExecutionGenerator; +import org.hibernate.generator.internal.OnExecutionCompositeGenerator; import org.hibernate.mapping.Component; import org.hibernate.mapping.Property; import org.hibernate.metamodel.mapping.AttributeMapping; @@ -20,7 +23,9 @@ 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; @@ -36,7 +41,8 @@ class CompositeGeneratorBuilder { private boolean hadBeforeExecutionGeneration; private boolean hadOnExecutionGeneration; - private final List generators = new ArrayList<>(); + private final Map generators = new HashMap<>(); + private final List generatorsByStateArrayPosition = new ArrayList<>(); public CompositeGeneratorBuilder(String entityName, Property mappingProperty, Dialect dialect) { this.entityName = entityName; @@ -44,8 +50,9 @@ public CompositeGeneratorBuilder(String entityName, Property mappingProperty, Di this.dialect = dialect; } - public void add(Generator generator) { - generators.add( generator ); + public void add(String attributeName, Generator generator) { + generators.put( attributeName, generator ); + generatorsByStateArrayPosition.add( generator ); if ( generator != null && generator.generatesSometimes() ) { if ( generator.generatedOnExecution() ) { @@ -71,94 +78,90 @@ else if ( hadOnExecutionGeneration ) { return createCompositeOnExecutionGenerator(); } else { - return new Generator() { + return new CompositeGenerator() { + @Override + public boolean generatedOnExecution() { + return false; + } + @Override public EnumSet getEventTypes() { return NONE; } + @Override - public boolean generatedOnExecution() { - return false; + public Generator getGenerator(int attributeStateArrayPosition) { + return new Generator() { + @Override + public EnumSet getEventTypes() { + return NONE; + } + + @Override + public boolean generatedOnExecution() { + return false; + } + }; } }; } } - private OnExecutionGenerator createCompositeOnExecutionGenerator() { + private Generator createCompositeOnExecutionGenerator() { final Component composite = (Component) mappingProperty.getValue(); - // the base-line values for the aggregated OnExecutionGenerator we will build here. final EnumSet eventTypes = EnumSet.noneOf(EventType.class); - boolean referenceColumns = false; + final List properties = composite.getProperties(); + boolean writePropertyValue = false; + boolean referenceColumnsInSql = false; final String[] columnValues = new String[composite.getColumnSpan()]; - - // start building the aggregate values int columnIndex = 0; - final List properties = composite.getProperties(); 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() - ); - } - eventTypes.addAll( generator.getEventTypes() ); - if ( generator.referenceColumnsInSql( dialect ) ) { - // override base-line value - referenceColumns = true; - final String[] referencedColumnValues = generator.getReferencedColumnValues( dialect ); - if ( referencedColumnValues != null ) { - final int span = property.getColumnSpan(); - 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() - ); + final Property property = properties.get( i ); +// final OnExecutionGenerator generator = (OnExecutionGenerator) generators.get( property.getName() ); + final OnExecutionGenerator generator = (OnExecutionGenerator) generatorsByStateArrayPosition.get( i ); + if ( generator != null ) { + if ( generator.generatesSometimes() ) { + eventTypes.addAll( generator.getEventTypes() ); + if ( generator.referenceColumnsInSql( dialect ) ) { + referenceColumnsInSql = true; + final String[] referencedColumnValues = generator.getReferencedColumnValues( dialect ); + if ( referencedColumnValues != null ) { + final int span = property.getColumnSpan(); + 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() + ); + } + arraycopy( referencedColumnValues, 0, columnValues, columnIndex, span ); + columnIndex += span; + } + } + if ( generator.writePropertyValue() ) { + writePropertyValue = true; } - arraycopy( referencedColumnValues, 0, columnValues, columnIndex, span ); - columnIndex += span; + } + else { + generators.put( property.getName(), null ); + generatorsByStateArrayPosition.set( i, null ); } } } - final boolean referenceColumnsInSql = referenceColumns; - - // then use the aggregated values to build an OnExecutionGenerator - return new OnExecutionGenerator() { - @Override - public EnumSet getEventTypes() { - return eventTypes; - } - - @Override - public boolean referenceColumnsInSql(Dialect dialect) { - return referenceColumnsInSql; - } - - @Override - public String[] getReferencedColumnValues(Dialect dialect) { - return columnValues; - } - - @Override - public boolean writePropertyValue() { - return false; - } - }; + return new OnExecutionCompositeGenerator( generatorsByStateArrayPosition, eventTypes, writePropertyValue, referenceColumnsInSql, columnValues ); } - private BeforeExecutionGenerator createCompositeBeforeExecutionGenerator() { + private CompositeBeforeExecutionGenerator createCompositeBeforeExecutionGenerator() { final Component composite = (Component) mappingProperty.getValue(); final EnumSet eventTypes = EnumSet.noneOf(EventType.class); final List properties = composite.getProperties(); for ( int i = 0; i < properties.size(); i++ ) { - final Generator generator = generators.get(i); + final Generator generator = generators.get(properties.get( i ).getName()); if ( generator != null ) { eventTypes.addAll( generator.getEventTypes() ); } } - return new BeforeExecutionGenerator() { + return new CompositeBeforeExecutionGenerator() { @Override public Object generate(SharedSessionContractImplementor session, Object owner, Object currentValue, EventType eventType) { final EntityPersister persister = session.getEntityPersister( entityName, owner ); @@ -170,7 +173,7 @@ public Object generate(SharedSessionContractImplementor session, Object owner, O if ( currentValue == null ) { final Object[] generatedValues = new Object[size]; for ( int i = 0; i < size; i++ ) { - final Generator generator = generators.get(i); + final Generator generator = generators.get( properties.get( i ).getName() ); if ( generator != null ) { generatedValues[i] = ((BeforeExecutionGenerator) generator) .generate( session, owner, null, eventType ); @@ -181,7 +184,7 @@ public Object generate(SharedSessionContractImplementor session, Object owner, O } else { for ( int i = 0; i < size; i++ ) { - final Generator generator = generators.get(i); + final Generator generator = generators.get( properties.get( i ).getName() ); if ( generator != null ) { final AttributeMapping attributeMapping = descriptor.getAttributeMapping(i); final Object value = attributeMapping.getPropertyAccess().getGetter().get( currentValue ); @@ -198,6 +201,11 @@ public Object generate(SharedSessionContractImplementor session, Object owner, O public EnumSet getEventTypes() { return eventTypes; } + + @Override + public Generator getGenerator(int attributeStateArrayPosition) { + return generatorsByStateArrayPosition.get( attributeStateArrayPosition ); + } }; } } diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java index adcdc3b5cc91..0dac00e36a0a 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java @@ -329,7 +329,8 @@ public EntityMetamodel( } else { generators[i] = generator; - if ( generatedWithNoParameter( generator ) ) { + if ( !( attribute instanceof EntityBasedCompositionAttribute ) + && generatedWithNoParameter( generator ) ) { propertyInsertability[i] = false; propertyUpdateability[i] = false; } @@ -520,10 +521,19 @@ private static Generator buildGenerator( } if ( mappingProperty.getValue() instanceof Component ) { final Dialect dialect = context.getDialect(); - final CompositeGeneratorBuilder builder = new CompositeGeneratorBuilder( entityName, mappingProperty, dialect ); + final CompositeGeneratorBuilder builder = new CompositeGeneratorBuilder( + entityName, + mappingProperty, + dialect + ); final Component component = (Component) mappingProperty.getValue(); for ( Property property : component.getProperties() ) { - builder.add( property.createGenerator( context ) ); + if ( property.isComposite() ) { + builder.add( property.getName(), buildGenerator( entityName, property, context ) ); + } + else { + builder.add( property.getName(), property.createGenerator( context ) ); + } } return builder.build(); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/AlwaysGeneratedTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/AlwaysGeneratedTest.java new file mode 100644 index 000000000000..8acf3a97a2b1 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/AlwaysGeneratedTest.java @@ -0,0 +1,386 @@ +package org.hibernate.orm.test.embeddable.generated; + +import java.sql.PreparedStatement; + +import org.hibernate.annotations.Generated; +import org.hibernate.annotations.GenerationTime; +import org.hibernate.dialect.PostgreSQLDialect; + +import org.hibernate.testing.jdbc.SQLStatementInspector; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.Jira; +import org.hibernate.testing.orm.junit.RequiresDialect; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +@RequiresDialect(PostgreSQLDialect.class) +@DomainModel( + annotatedClasses = { + AlwaysGeneratedTest.TestEntity.class + } +) +@SessionFactory( + exportSchema = false, + useCollectingStatementInspector = true +) +@Jira("HHH-16957") +public class AlwaysGeneratedTest { + + private final static String NON_GENERATED_PROP_0 = "a"; + private final static String UPDATED_NON_GENERATED_PROP_0 = "b"; + private final static int NON_GENERATED_PROP_1 = 20; + private final static int UPDATED_NON_GENERATED_PROP_1 = 20; + + private final static int ALWAYS_GENERATED_PROP_0 = 1; + private final static int ALWAYS_GENERATED_PROP_WRITABLE_0 = 2; + private final static int ALWAYS_GENERATED_PROP_1 = 3; + private final static int ALWAYS_GENERATED_PROP_WRITABLE_1 = 4; + + private final static String NON_GENERATED_PROP_0_COLUMN = "non_generate_prop_0"; + private final static String ALWAYS_GENERATED_PROP_0_COLUMN = "always_generated_prop_0"; + private final static String ALWAYS_GENERATED_PROP_WRITABLE_0_COLUMN = "always_generated_prop_writable_0"; + + private final static String NON_GENERATED_PROP_1_COLUMN = "non_generate_prop_1"; + private final static String ALWAYS_GENERATED_PROP_1_COLUMN = "always_generated_prop_1"; + private final static String ALWAYS_GENERATED_PROP_WRITABLE_1_COLUMN = "always_generated_prop_writable_1"; + + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( + session -> + session.doWork( connection -> { + PreparedStatement createTablePreparedStatement = connection.prepareStatement( + "create table IF NOT EXISTS test_entity (" + + " id integer not null, " + + NON_GENERATED_PROP_0_COLUMN + " varchar(255), " + + ALWAYS_GENERATED_PROP_0_COLUMN + " integer generated always as ( " + ALWAYS_GENERATED_PROP_0 + " ) stored, " + + ALWAYS_GENERATED_PROP_WRITABLE_0_COLUMN + " integer, " + + NON_GENERATED_PROP_1_COLUMN + " integer, " + + ALWAYS_GENERATED_PROP_1_COLUMN + " integer generated always as ( " + ALWAYS_GENERATED_PROP_1 + " ) stored, " + + ALWAYS_GENERATED_PROP_WRITABLE_1_COLUMN + " integer, " + + " primary key (id)" + + " )" ); + try { + createTablePreparedStatement.execute(); + } + finally { + createTablePreparedStatement.close(); + } + + PreparedStatement afterInsertFunctionPreparedStatement = connection.prepareStatement( + "create or replace function afterInsert() returns trigger as $afterInsert$ begin " + + "update test_entity set " + + ALWAYS_GENERATED_PROP_WRITABLE_0_COLUMN + " = " + ALWAYS_GENERATED_PROP_WRITABLE_0 + " , " + + ALWAYS_GENERATED_PROP_WRITABLE_1_COLUMN + " = " + ALWAYS_GENERATED_PROP_WRITABLE_1 + + " where id = NEW.id; return NEW; END; $afterInsert$ LANGUAGE plpgsql" ); + try { + afterInsertFunctionPreparedStatement.execute(); + } + finally { + afterInsertFunctionPreparedStatement.close(); + } + + PreparedStatement afterInsertTriggerPreparedStatement = connection.prepareStatement( + "create or replace trigger after_insert_t after insert on test_entity for each row execute function afterInsert();" ); + try { + afterInsertTriggerPreparedStatement.execute(); + } + finally { + afterInsertTriggerPreparedStatement.close(); + } + } ) + ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( + session -> + session.doWork( connection -> { + + PreparedStatement dropAfterInsertTrigger = connection + .prepareStatement( "drop trigger IF EXISTS after_insert_t on test_entity" ); + try { + dropAfterInsertTrigger.execute(); + } + finally { + dropAfterInsertTrigger.close(); + } + + PreparedStatement dropAfterInsertFunction = connection + .prepareStatement( "drop function IF EXISTS afterInsert" ); + try { + dropAfterInsertFunction.execute(); + } + finally { + dropAfterInsertFunction.close(); + } + + PreparedStatement dropTable = connection.prepareStatement( "drop table test_entity" ); + try { + dropTable.execute(); + } + finally { + dropTable.close(); + } + } ) + ); + } + + @Test + public void testPersistAndFind(SessionFactoryScope scope) { + SQLStatementInspector sqlStatementInspector = (SQLStatementInspector) scope.getStatementInspector(); + sqlStatementInspector.clear(); + + TestEntity testEntity = new TestEntity( + 1, + NON_GENERATED_PROP_0, + new EmbeddableValue( NON_GENERATED_PROP_1 ) + ); + + scope.inTransaction( + session -> + session.persist( testEntity ) + ); + + sqlStatementInspector.assertExecutedCount( 2 ); + sqlStatementInspector.assertIsInsert( 0 ); + sqlStatementInspector.assertIsSelect( 1 ); + + assertThatInsertIsCorrect( sqlStatementInspector.getSqlQueries().get( 0 ) ); + assertThatSelectOnInsertIsCorrect( + sqlStatementInspector.getSqlQueries().get( 1 ), + testEntity + ); + + sqlStatementInspector.clear(); + scope.inTransaction( + session -> { + TestEntity found = session.find( TestEntity.class, 1 ); + + sqlStatementInspector.assertExecutedCount( 1 ); + sqlStatementInspector.assertIsSelect( 0 ); + assertThatSelectStatementIsCorrect( sqlStatementInspector.getSqlQueries().get( 0 ) ); + + sqlStatementInspector.clear(); + + found.setName( UPDATED_NON_GENERATED_PROP_0 ); + found.getEmbeddableValue().setNonGeneratedProperty1( UPDATED_NON_GENERATED_PROP_1 ); + session.flush(); + + sqlStatementInspector.assertExecutedCount( 2 ); + sqlStatementInspector.assertIsUpdate( 0 ); + assertThatUpdateStatementIsCorrect( sqlStatementInspector.getSqlQueries().get( 0 ) ); + assertThatSelectOnUpdateIsCorrect( + sqlStatementInspector.getSqlQueries().get( 1 ), + found + ); + } + ); + } + + private static void assertThatInsertIsCorrect(String insertQuery) { + assertThat( insertQuery ).contains( NON_GENERATED_PROP_0_COLUMN ); + assertThat( insertQuery ).contains( ALWAYS_GENERATED_PROP_WRITABLE_0_COLUMN ); + assertThat( insertQuery ).contains( NON_GENERATED_PROP_1_COLUMN ); + assertThat( insertQuery ).contains( ALWAYS_GENERATED_PROP_WRITABLE_1_COLUMN ); + + assertThat( insertQuery ).doesNotContain( ALWAYS_GENERATED_PROP_0_COLUMN ); + assertThat( insertQuery ).doesNotContain( ALWAYS_GENERATED_PROP_1_COLUMN ); + } + + private static void assertThatSelectOnInsertIsCorrect(String selectQuery, TestEntity testEntity) { + assertThat( selectQuery ).contains( ALWAYS_GENERATED_PROP_0_COLUMN ); + assertThat( selectQuery ).contains( ALWAYS_GENERATED_PROP_1_COLUMN ); + assertThat( selectQuery ).contains( ALWAYS_GENERATED_PROP_WRITABLE_0_COLUMN ); + assertThat( selectQuery ).contains( ALWAYS_GENERATED_PROP_WRITABLE_1_COLUMN ); + + assertThat( selectQuery ).doesNotContain( NON_GENERATED_PROP_0_COLUMN ); +// assertThat( selectQuery ).doesNotContain( NON_GENERATED_PROP_1_COLUMN ); + + assertThat( testEntity.getName() ).isEqualTo( NON_GENERATED_PROP_0 ); + assertThat( testEntity.getAlwaysGeneratedProp0() ).isEqualTo( ALWAYS_GENERATED_PROP_0 ); + assertThat( testEntity.getAlwaysGeneratedProp01() ).isEqualTo( ALWAYS_GENERATED_PROP_WRITABLE_0 ); + + EmbeddableValue embeddableValue = testEntity.getEmbeddableValue(); + assertThat( embeddableValue.getNonGeneratedProperty1() ) + .isEqualTo( NON_GENERATED_PROP_1 ); + assertThat( embeddableValue.getAlwaysGeneratedProperty1() ) + .isEqualTo( ALWAYS_GENERATED_PROP_1 ); + assertThat( embeddableValue.getAlwaysGeneratedProperty11() ) + .isEqualTo( ALWAYS_GENERATED_PROP_WRITABLE_1 ); + } + + private static void assertThatSelectStatementIsCorrect(String selectQuery) { + assertThat( selectQuery ).contains( NON_GENERATED_PROP_0_COLUMN ); + assertThat( selectQuery ).contains( ALWAYS_GENERATED_PROP_0_COLUMN ); + assertThat( selectQuery ).contains( ALWAYS_GENERATED_PROP_WRITABLE_0_COLUMN ); + assertThat( selectQuery ).contains( NON_GENERATED_PROP_1_COLUMN ); + assertThat( selectQuery ).contains( ALWAYS_GENERATED_PROP_1_COLUMN ); + assertThat( selectQuery ).contains( ALWAYS_GENERATED_PROP_WRITABLE_1_COLUMN ); + } + + private static void assertThatUpdateStatementIsCorrect(String updateQuery) { + assertThat( updateQuery ).contains( NON_GENERATED_PROP_0_COLUMN ); + + assertThat( updateQuery ).contains( ALWAYS_GENERATED_PROP_WRITABLE_0_COLUMN ); + assertThat( updateQuery ).contains( NON_GENERATED_PROP_1_COLUMN ); + assertThat( updateQuery ).contains( ALWAYS_GENERATED_PROP_WRITABLE_1_COLUMN ); + + assertThat( updateQuery ).doesNotContain( ALWAYS_GENERATED_PROP_0_COLUMN ); + assertThat( updateQuery ).doesNotContain( ALWAYS_GENERATED_PROP_1_COLUMN ); + } + + private static void assertThatSelectOnUpdateIsCorrect(String selectQuery, TestEntity testEntity) { + assertThat( selectQuery ).contains( ALWAYS_GENERATED_PROP_0_COLUMN ); + assertThat( selectQuery ).contains( ALWAYS_GENERATED_PROP_1_COLUMN ); + assertThat( selectQuery ).contains( ALWAYS_GENERATED_PROP_WRITABLE_0_COLUMN ); + assertThat( selectQuery ).contains( ALWAYS_GENERATED_PROP_WRITABLE_1_COLUMN ); + + assertThat( selectQuery ).doesNotContain( NON_GENERATED_PROP_0_COLUMN ); +// assertThat( selectQuery ).doesNotContain( NON_GENERATED_PROP_1_COLUMN ); + + assertThat( testEntity.getName() ).isEqualTo( UPDATED_NON_GENERATED_PROP_0 ); + assertThat( testEntity.getAlwaysGeneratedProp0() ).isEqualTo( ALWAYS_GENERATED_PROP_0 ); + assertThat( testEntity.getAlwaysGeneratedProp01() ).isEqualTo( ALWAYS_GENERATED_PROP_WRITABLE_0 ); + + EmbeddableValue embeddableValue = testEntity.getEmbeddableValue(); + assertThat( embeddableValue.getNonGeneratedProperty1() ) + .isEqualTo( UPDATED_NON_GENERATED_PROP_1 ); + assertThat( embeddableValue.getAlwaysGeneratedProperty1() ) + .isEqualTo( ALWAYS_GENERATED_PROP_1 ); + assertThat( embeddableValue.getAlwaysGeneratedProperty11() ) + .isEqualTo( ALWAYS_GENERATED_PROP_WRITABLE_1 ); + } + + @Entity(name = "TestEntity") + @Table(name = "test_entity") + public static class TestEntity { + + @Id + private Integer id; + @Column(name = NON_GENERATED_PROP_0_COLUMN) + private String name; + + @Column(name = ALWAYS_GENERATED_PROP_0_COLUMN) + @Generated(GenerationTime.ALWAYS) + private Integer alwaysGeneratedProp0; + + @Column(name = ALWAYS_GENERATED_PROP_WRITABLE_0_COLUMN) + @Generated(value = GenerationTime.ALWAYS, writable = true) + private Integer alwaysGeneratedProp01; + + @Embedded + private EmbeddableValue embeddableValue; + + public TestEntity() { + } + + public TestEntity( + Integer id, + String name, + EmbeddableValue embeddableValue) { + this.id = id; + this.name = name; + this.embeddableValue = embeddableValue; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getAlwaysGeneratedProp0() { + return alwaysGeneratedProp0; + } + + public void setAlwaysGeneratedProp0(Integer alwaysGeneratedProp0) { + this.alwaysGeneratedProp0 = alwaysGeneratedProp0; + } + + public Integer getAlwaysGeneratedProp01() { + return alwaysGeneratedProp01; + } + + public void setAlwaysGeneratedProp01(Integer alwaysGeneratedProp01) { + this.alwaysGeneratedProp01 = alwaysGeneratedProp01; + } + + public EmbeddableValue getEmbeddableValue() { + return embeddableValue; + } + + public void setEmbeddableValue(EmbeddableValue embeddableValue) { + this.embeddableValue = embeddableValue; + } + } + + @Embeddable + public static class EmbeddableValue { + @Column(name = NON_GENERATED_PROP_1_COLUMN) + private Integer nonGeneratedProperty1; + + @Generated(GenerationTime.ALWAYS) + @Column(name = ALWAYS_GENERATED_PROP_1_COLUMN) + private Integer alwaysGeneratedProperty1; + + @Generated(value = GenerationTime.ALWAYS, writable = true) + @Column(name = ALWAYS_GENERATED_PROP_WRITABLE_1_COLUMN) + private Integer alwaysGeneratedProperty11; + + public EmbeddableValue() { + } + + public EmbeddableValue(Integer nonGeneratedProperty1) { + this.nonGeneratedProperty1 = nonGeneratedProperty1; + } + + + public Integer getNonGeneratedProperty1() { + return nonGeneratedProperty1; + } + + public void setNonGeneratedProperty1(Integer nonGeneratedProperty1) { + this.nonGeneratedProperty1 = nonGeneratedProperty1; + } + + public Integer getAlwaysGeneratedProperty1() { + return alwaysGeneratedProperty1; + } + + public void setAlwaysGeneratedProperty1(Integer alwaysGeneratedProperty1) { + this.alwaysGeneratedProperty1 = alwaysGeneratedProperty1; + } + + public Integer getAlwaysGeneratedProperty11() { + return alwaysGeneratedProperty11; + } + + public void setAlwaysGeneratedProperty11(Integer alwaysGeneratedProperty11) { + this.alwaysGeneratedProperty11 = alwaysGeneratedProperty11; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/DynamicUpdateInsertAndGeneratedValuesTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/DynamicUpdateInsertAndGeneratedValuesTest.java new file mode 100644 index 000000000000..7296b0dfa359 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/DynamicUpdateInsertAndGeneratedValuesTest.java @@ -0,0 +1,441 @@ +package org.hibernate.orm.test.embeddable.generated; + +import java.sql.PreparedStatement; + +import org.hibernate.annotations.DynamicInsert; +import org.hibernate.annotations.DynamicUpdate; +import org.hibernate.annotations.Generated; +import org.hibernate.annotations.GenerationTime; +import org.hibernate.dialect.PostgreSQLDialect; + +import org.hibernate.testing.jdbc.SQLStatementInspector; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.Jira; +import org.hibernate.testing.orm.junit.RequiresDialect; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +@RequiresDialect(PostgreSQLDialect.class) +@DomainModel( + annotatedClasses = { + DynamicUpdateInsertAndGeneratedValuesTest.TestEntity.class + } +) +@SessionFactory( + exportSchema = false, + useCollectingStatementInspector = true +) +@Jira("HHH-16957") +public class DynamicUpdateInsertAndGeneratedValuesTest { + private final static String NON_GENERATED_PROP_0 = "a"; + private final static String UPDATED_NON_GENERATED_PROP_0 = "b"; + private final static int NON_GENERATED_PROP_1 = 20; + private final static int UPDATED_NON_GENERATED_PROP_1 = 20; + + private final static int ALWAYS_GENERATED_PROP_0 = 1; + private final static int ALWAYS_GENERATED_PROP_WRITABLE_0 = 2; + private final static int ALWAYS_GENERATED_PROP_1 = 3; + private final static int ALWAYS_GENERATED_PROP_WRITABLE_1 = 4; + private final static int INSERT_GENERATED_PROP_1 = 5; + private final static int INSERT_GENERATED_PROP_WRITABLE_1 = 6; + + private final static String NON_GENERATED_PROP_0_COLUMN = "non_generate_prop_0"; + private final static String ALWAYS_GENERATED_PROP_0_COLUMN = "always_generated_prop_0"; + private final static String ALWAYS_GENERATED_PROP_WRITABLE_0_COLUMN = "always_generated_prop_writable_0"; + + private final static String NON_GENERATED_PROP_1_COLUMN = "non_generate_prop_1"; + private final static String ALWAYS_GENERATED_PROP_1_COLUMN = "always_generated_prop_1"; + private final static String ALWAYS_GENERATED_PROP_WRITABLE_1_COLUMN = "always_generated_prop_writable_1"; + private final static String INSERT_GENERATED_PROP_1_COLUMN = "insert_generated_prop_1"; + private final static String INSERT_GENERATED_PROP_WRITABLE_1_COLUMN = "insert_generated_prop_writable_1"; + + + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( + session -> + session.doWork( connection -> { + PreparedStatement createTablePreparedStatement = connection.prepareStatement( + "create table IF NOT EXISTS test_entity (" + + " id integer not null, " + + NON_GENERATED_PROP_0_COLUMN + " varchar(255), " + + ALWAYS_GENERATED_PROP_0_COLUMN + " integer generated always as ( " + ALWAYS_GENERATED_PROP_0 + " ) stored, " + + ALWAYS_GENERATED_PROP_WRITABLE_0_COLUMN + " integer, " + + NON_GENERATED_PROP_1_COLUMN + " integer, " + + ALWAYS_GENERATED_PROP_1_COLUMN + " integer generated always as ( " + ALWAYS_GENERATED_PROP_1 + " ) stored, " + + ALWAYS_GENERATED_PROP_WRITABLE_1_COLUMN + " integer, " + + INSERT_GENERATED_PROP_1_COLUMN + " integer generated always as ( " + INSERT_GENERATED_PROP_1 + " ) stored, " + + INSERT_GENERATED_PROP_WRITABLE_1_COLUMN + " integer, " + + " primary key (id)" + + " )" ); + try { + createTablePreparedStatement.execute(); + } + finally { + createTablePreparedStatement.close(); + } + + PreparedStatement afterInsertFunctionPreparedStatement = connection.prepareStatement( + "create or replace function afterInsert() returns trigger as $afterInsert$ begin " + + "update test_entity set " + + ALWAYS_GENERATED_PROP_WRITABLE_0_COLUMN + " = " + ALWAYS_GENERATED_PROP_WRITABLE_0 + " , " + + ALWAYS_GENERATED_PROP_WRITABLE_1_COLUMN + " = " + ALWAYS_GENERATED_PROP_WRITABLE_1 + " , " + + INSERT_GENERATED_PROP_WRITABLE_1_COLUMN + " = " + INSERT_GENERATED_PROP_WRITABLE_1 + + " where id = NEW.id; return NEW; END; $afterInsert$ LANGUAGE plpgsql" ); + try { + afterInsertFunctionPreparedStatement.execute(); + } + finally { + afterInsertFunctionPreparedStatement.close(); + } + + PreparedStatement afterInsertTriggerPreparedStatement = connection.prepareStatement( + "create or replace trigger after_insert_t after insert on test_entity for each row execute function afterInsert();" ); + try { + afterInsertTriggerPreparedStatement.execute(); + } + finally { + afterInsertTriggerPreparedStatement.close(); + } + } ) + ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( + session -> + session.doWork( connection -> { + + PreparedStatement dropAfterInsertTrigger = connection + .prepareStatement( "drop trigger IF EXISTS after_insert_t on test_entity" ); + try { + dropAfterInsertTrigger.execute(); + } + finally { + dropAfterInsertTrigger.close(); + } + + PreparedStatement dropAfterInsertFunction = connection + .prepareStatement( "drop function IF EXISTS afterInsert" ); + try { + dropAfterInsertFunction.execute(); + } + finally { + dropAfterInsertFunction.close(); + } + + PreparedStatement dropTable = connection.prepareStatement( "drop table test_entity" ); + try { + dropTable.execute(); + } + finally { + dropTable.close(); + } + } ) + ); + } + + @Test + public void testPersistAndFind(SessionFactoryScope scope) { + SQLStatementInspector sqlStatementInspector = (SQLStatementInspector) scope.getStatementInspector(); + sqlStatementInspector.clear(); + + TestEntity testEntity = new TestEntity( + 1, + NON_GENERATED_PROP_0, + new EmbeddableValue( NON_GENERATED_PROP_1 ) + ); + + scope.inTransaction( + session -> + session.persist( testEntity ) + ); + + sqlStatementInspector.assertExecutedCount( 2 ); + sqlStatementInspector.assertIsInsert( 0 ); + sqlStatementInspector.assertIsSelect( 1 ); + + assertThatInsertIsCorrect( sqlStatementInspector.getSqlQueries().get( 0 ) ); + assertThatSelectOnInsertIsCorrect( + sqlStatementInspector.getSqlQueries().get( 1 ), + testEntity + ); + + sqlStatementInspector.clear(); + scope.inTransaction( + session -> { + TestEntity found = session.find( TestEntity.class, 1 ); + + sqlStatementInspector.assertExecutedCount( 1 ); + sqlStatementInspector.assertIsSelect( 0 ); + assertThatSelectStatementIsCorrect( sqlStatementInspector.getSqlQueries().get( 0 ) ); + + sqlStatementInspector.clear(); + + found.setName( UPDATED_NON_GENERATED_PROP_0 ); + found.getEmbeddableValue().setNonGeneratedProperty1( UPDATED_NON_GENERATED_PROP_1 ); + session.flush(); + + sqlStatementInspector.assertExecutedCount( 2 ); + sqlStatementInspector.assertIsUpdate( 0 ); + assertThatUpdateStatementIsCorrect( sqlStatementInspector.getSqlQueries().get( 0 ) ); + assertThatSelectOnUpdateIsCorrect( + sqlStatementInspector.getSqlQueries().get( 1 ), + found + ); + } + ); + } + + private static void assertThatInsertIsCorrect(String insertQuery) { + assertThat( insertQuery ).contains( NON_GENERATED_PROP_0_COLUMN ); + assertThat( insertQuery ).contains( NON_GENERATED_PROP_1_COLUMN ); + + assertThat( insertQuery ).doesNotContain( ALWAYS_GENERATED_PROP_WRITABLE_1_COLUMN ); + assertThat( insertQuery ).doesNotContain( INSERT_GENERATED_PROP_WRITABLE_1_COLUMN ); + assertThat( insertQuery ).doesNotContain( ALWAYS_GENERATED_PROP_WRITABLE_0_COLUMN ); + assertThat( insertQuery ).doesNotContain( ALWAYS_GENERATED_PROP_0_COLUMN ); + assertThat( insertQuery ).doesNotContain( ALWAYS_GENERATED_PROP_1_COLUMN ); + assertThat( insertQuery ).doesNotContain( INSERT_GENERATED_PROP_1_COLUMN ); + } + + private static void assertThatSelectOnInsertIsCorrect(String selectQuery, TestEntity testEntity) { + assertThat( selectQuery ).contains( ALWAYS_GENERATED_PROP_0_COLUMN ); + assertThat( selectQuery ).contains( ALWAYS_GENERATED_PROP_1_COLUMN ); + + assertThat( selectQuery ).contains( ALWAYS_GENERATED_PROP_WRITABLE_0_COLUMN ); + assertThat( selectQuery ).contains( ALWAYS_GENERATED_PROP_WRITABLE_1_COLUMN ); + + assertThat( selectQuery ).contains( INSERT_GENERATED_PROP_1_COLUMN ); + assertThat( selectQuery ).contains( INSERT_GENERATED_PROP_WRITABLE_1_COLUMN ); + + assertThat( selectQuery ).doesNotContain( NON_GENERATED_PROP_0_COLUMN ); +// assertThat( selectQuery ).doesNotContain( NON_GENERATED_PROP_1_COLUMN ); + + assertThat( testEntity.getName() ).isEqualTo( NON_GENERATED_PROP_0 ); + assertThat( testEntity.getAlwaysGeneratedProp0() ).isEqualTo( ALWAYS_GENERATED_PROP_0 ); + assertThat( testEntity.getAlwaysGeneratedProp01() ).isEqualTo( ALWAYS_GENERATED_PROP_WRITABLE_0 ); + + EmbeddableValue embeddableValue = testEntity.getEmbeddableValue(); + assertThat( embeddableValue.getNonGeneratedProperty1() ) + .isEqualTo( NON_GENERATED_PROP_1 ); + assertThat( embeddableValue.getAlwaysGeneratedProperty1() ) + .isEqualTo( ALWAYS_GENERATED_PROP_1 ); + assertThat( embeddableValue.getAlwaysGeneratedProperty11() ) + .isEqualTo( ALWAYS_GENERATED_PROP_WRITABLE_1 ); + assertThat( embeddableValue.getInsertGeneratedProperty1() ) + .isEqualTo( INSERT_GENERATED_PROP_1 ); + assertThat( embeddableValue.getInsertGeneratedProperty11() ) + .isEqualTo( INSERT_GENERATED_PROP_WRITABLE_1 ); + } + + private static void assertThatSelectStatementIsCorrect(String selectQuery) { + assertThat( selectQuery ).contains( NON_GENERATED_PROP_0_COLUMN ); + assertThat( selectQuery ).contains( ALWAYS_GENERATED_PROP_0_COLUMN ); + assertThat( selectQuery ).contains( ALWAYS_GENERATED_PROP_WRITABLE_0_COLUMN ); + assertThat( selectQuery ).contains( NON_GENERATED_PROP_1_COLUMN ); + assertThat( selectQuery ).contains( ALWAYS_GENERATED_PROP_1_COLUMN ); + assertThat( selectQuery ).contains( ALWAYS_GENERATED_PROP_WRITABLE_1_COLUMN ); + assertThat( selectQuery ).contains( INSERT_GENERATED_PROP_1_COLUMN ); + assertThat( selectQuery ).contains( INSERT_GENERATED_PROP_WRITABLE_1_COLUMN ); + } + + private static void assertThatUpdateStatementIsCorrect(String updateQuery) { + assertThat( updateQuery ).contains( NON_GENERATED_PROP_0_COLUMN ); + + assertThat( updateQuery ).doesNotContain( ALWAYS_GENERATED_PROP_WRITABLE_0_COLUMN ); + assertThat( updateQuery ).doesNotContain( NON_GENERATED_PROP_1_COLUMN ); + assertThat( updateQuery ).doesNotContain( ALWAYS_GENERATED_PROP_WRITABLE_1_COLUMN ); + assertThat( updateQuery ).doesNotContain( INSERT_GENERATED_PROP_WRITABLE_1_COLUMN ); + + assertThat( updateQuery ).doesNotContain( ALWAYS_GENERATED_PROP_0_COLUMN ); + assertThat( updateQuery ).doesNotContain( ALWAYS_GENERATED_PROP_1_COLUMN ); + assertThat( updateQuery ).doesNotContain( INSERT_GENERATED_PROP_1_COLUMN ); + } + + private static void assertThatSelectOnUpdateIsCorrect(String selectQuery, TestEntity testEntity) { + assertThat( selectQuery ).contains( ALWAYS_GENERATED_PROP_0_COLUMN ); + assertThat( selectQuery ).contains( ALWAYS_GENERATED_PROP_1_COLUMN ); + assertThat( selectQuery ).contains( ALWAYS_GENERATED_PROP_WRITABLE_0_COLUMN ); + assertThat( selectQuery ).contains( ALWAYS_GENERATED_PROP_WRITABLE_1_COLUMN ); + + assertThat( selectQuery ).doesNotContain( NON_GENERATED_PROP_0_COLUMN ); +// assertThat( selectQuery ).doesNotContain( NON_GENERATED_PROP_1_COLUMN ); +// assertThat( selectQuery ).contains( INSERT_GENERATED_PROP_WRITABLE_1_COLUMN ); +// assertThat( selectQuery ).doesNotContain( INSERT_GENERATED_PROP_1_COLUMN ); + + assertThat( testEntity.getName() ).isEqualTo( UPDATED_NON_GENERATED_PROP_0 ); + assertThat( testEntity.getAlwaysGeneratedProp0() ).isEqualTo( ALWAYS_GENERATED_PROP_0 ); + assertThat( testEntity.getAlwaysGeneratedProp01() ).isEqualTo( ALWAYS_GENERATED_PROP_WRITABLE_0 ); + + EmbeddableValue embeddableValue = testEntity.getEmbeddableValue(); + assertThat( embeddableValue.getNonGeneratedProperty1() ) + .isEqualTo( UPDATED_NON_GENERATED_PROP_1 ); + assertThat( embeddableValue.getAlwaysGeneratedProperty1() ) + .isEqualTo( ALWAYS_GENERATED_PROP_1 ); + assertThat( embeddableValue.getAlwaysGeneratedProperty11() ) + .isEqualTo( ALWAYS_GENERATED_PROP_WRITABLE_1 ); + assertThat( embeddableValue.getInsertGeneratedProperty1() ) + .isEqualTo( INSERT_GENERATED_PROP_1 ); + assertThat( embeddableValue.getInsertGeneratedProperty11() ) + .isEqualTo( INSERT_GENERATED_PROP_WRITABLE_1 ); + } + + @Entity(name = "TestEntity") + @Table(name = "test_entity") + @DynamicInsert + @DynamicUpdate + public static class TestEntity { + + @Id + private Integer id; + @Column(name = NON_GENERATED_PROP_0_COLUMN) + private String name; + + @Column(name = ALWAYS_GENERATED_PROP_0_COLUMN) + @Generated(GenerationTime.ALWAYS) + private Integer alwaysGeneratedProp0; + + @Column(name = ALWAYS_GENERATED_PROP_WRITABLE_0_COLUMN) + @Generated(value = GenerationTime.ALWAYS, writable = true) + private Integer alwaysGeneratedProp01; + + @Embedded + private EmbeddableValue embeddableValue; + + public TestEntity() { + } + + public TestEntity( + Integer id, + String name, + EmbeddableValue embeddableValue) { + this.id = id; + this.name = name; + this.embeddableValue = embeddableValue; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getAlwaysGeneratedProp0() { + return alwaysGeneratedProp0; + } + + public void setAlwaysGeneratedProp0(Integer alwaysGeneratedProp0) { + this.alwaysGeneratedProp0 = alwaysGeneratedProp0; + } + + public Integer getAlwaysGeneratedProp01() { + return alwaysGeneratedProp01; + } + + public void setAlwaysGeneratedProp01(Integer alwaysGeneratedProp01) { + this.alwaysGeneratedProp01 = alwaysGeneratedProp01; + } + + public EmbeddableValue getEmbeddableValue() { + return embeddableValue; + } + + public void setEmbeddableValue(EmbeddableValue embeddableValue) { + this.embeddableValue = embeddableValue; + } + } + + @Embeddable + public static class EmbeddableValue { + @Column(name = NON_GENERATED_PROP_1_COLUMN) + private Integer nonGeneratedProperty1; + + @Generated(GenerationTime.ALWAYS) + @Column(name = ALWAYS_GENERATED_PROP_1_COLUMN) + private Integer alwaysGeneratedProperty1; + + @Generated(value = GenerationTime.ALWAYS, writable = true) + @Column(name = ALWAYS_GENERATED_PROP_WRITABLE_1_COLUMN) + private Integer alwaysGeneratedProperty11; + + @Generated(GenerationTime.INSERT) + @Column(name = INSERT_GENERATED_PROP_1_COLUMN) + private Integer insertGeneratedProperty1; + + @Generated(value = GenerationTime.INSERT, writable = true) + @Column(name = INSERT_GENERATED_PROP_WRITABLE_1_COLUMN) + private Integer insertGeneratedProperty11; + + public Integer getInsertGeneratedProperty1() { + return insertGeneratedProperty1; + } + + public void setInsertGeneratedProperty1(Integer insertGeneratedProperty1) { + this.insertGeneratedProperty1 = insertGeneratedProperty1; + } + + public Integer getInsertGeneratedProperty11() { + return insertGeneratedProperty11; + } + + public void setInsertGeneratedProperty11(Integer insertGeneratedProperty11) { + this.insertGeneratedProperty11 = insertGeneratedProperty11; + } + + public EmbeddableValue() { + } + + public EmbeddableValue(Integer nonGeneratedProperty1) { + this.nonGeneratedProperty1 = nonGeneratedProperty1; + } + + + public Integer getNonGeneratedProperty1() { + return nonGeneratedProperty1; + } + + public void setNonGeneratedProperty1(Integer nonGeneratedProperty1) { + this.nonGeneratedProperty1 = nonGeneratedProperty1; + } + + public Integer getAlwaysGeneratedProperty1() { + return alwaysGeneratedProperty1; + } + + public void setAlwaysGeneratedProperty1(Integer alwaysGeneratedProperty1) { + this.alwaysGeneratedProperty1 = alwaysGeneratedProperty1; + } + + public Integer getAlwaysGeneratedProperty11() { + return alwaysGeneratedProperty11; + } + + public void setAlwaysGeneratedProperty11(Integer alwaysGeneratedProperty11) { + this.alwaysGeneratedProperty11 = alwaysGeneratedProperty11; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/GeneratedValuesTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/GeneratedValuesTest.java new file mode 100644 index 000000000000..e2fdccab42c4 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/GeneratedValuesTest.java @@ -0,0 +1,437 @@ +package org.hibernate.orm.test.embeddable.generated; + +import java.sql.PreparedStatement; + +import org.hibernate.annotations.Generated; +import org.hibernate.annotations.GenerationTime; +import org.hibernate.dialect.PostgreSQLDialect; + +import org.hibernate.testing.jdbc.SQLStatementInspector; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.Jira; +import org.hibernate.testing.orm.junit.RequiresDialect; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +@RequiresDialect(PostgreSQLDialect.class) +@DomainModel( + annotatedClasses = { + GeneratedValuesTest.TestEntity.class + } +) +@SessionFactory( + exportSchema = false, + useCollectingStatementInspector = true +) +@Jira("HHH-16957") +public class GeneratedValuesTest { + private final static String NON_GENERATED_PROP_0 = "a"; + private final static String UPDATED_NON_GENERATED_PROP_0 = "b"; + private final static int NON_GENERATED_PROP_1 = 20; + private final static int UPDATED_NON_GENERATED_PROP_1 = 20; + + private final static int ALWAYS_GENERATED_PROP_0 = 1; + private final static int ALWAYS_GENERATED_PROP_WRITABLE_0 = 2; + private final static int ALWAYS_GENERATED_PROP_1 = 3; + private final static int ALWAYS_GENERATED_PROP_WRITABLE_1 = 4; + private final static int INSERT_GENERATED_PROP_1 = 5; + private final static int INSERT_GENERATED_PROP_WRITABLE_1 = 6; + + private final static String NON_GENERATED_PROP_0_COLUMN = "non_generate_prop_0"; + private final static String ALWAYS_GENERATED_PROP_0_COLUMN = "always_generated_prop_0"; + private final static String ALWAYS_GENERATED_PROP_WRITABLE_0_COLUMN = "always_generated_prop_writable_0"; + + private final static String NON_GENERATED_PROP_1_COLUMN = "non_generate_prop_1"; + private final static String ALWAYS_GENERATED_PROP_1_COLUMN = "always_generated_prop_1"; + private final static String ALWAYS_GENERATED_PROP_WRITABLE_1_COLUMN = "always_generated_prop_writable_1"; + private final static String INSERT_GENERATED_PROP_1_COLUMN = "insert_generated_prop_1"; + private final static String INSERT_GENERATED_PROP_WRITABLE_1_COLUMN = "insert_generated_prop_writable_1"; + + + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( + session -> + session.doWork( connection -> { + PreparedStatement createTablePreparedStatement = connection.prepareStatement( + "create table IF NOT EXISTS test_entity (" + + " id integer not null, " + + NON_GENERATED_PROP_0_COLUMN + " varchar(255), " + + ALWAYS_GENERATED_PROP_0_COLUMN + " integer generated always as ( " + ALWAYS_GENERATED_PROP_0 + " ) stored, " + + ALWAYS_GENERATED_PROP_WRITABLE_0_COLUMN + " integer, " + + NON_GENERATED_PROP_1_COLUMN + " integer, " + + ALWAYS_GENERATED_PROP_1_COLUMN + " integer generated always as ( " + ALWAYS_GENERATED_PROP_1 + " ) stored, " + + ALWAYS_GENERATED_PROP_WRITABLE_1_COLUMN + " integer, " + + INSERT_GENERATED_PROP_1_COLUMN + " integer generated always as ( " + INSERT_GENERATED_PROP_1 + " ) stored, " + + INSERT_GENERATED_PROP_WRITABLE_1_COLUMN + " integer, " + + " primary key (id)" + + " )" ); + try { + createTablePreparedStatement.execute(); + } + finally { + createTablePreparedStatement.close(); + } + + PreparedStatement afterInsertFunctionPreparedStatement = connection.prepareStatement( + "create or replace function afterInsert() returns trigger as $afterInsert$ begin " + + "update test_entity set " + + ALWAYS_GENERATED_PROP_WRITABLE_0_COLUMN + " = " + ALWAYS_GENERATED_PROP_WRITABLE_0 + " , " + + ALWAYS_GENERATED_PROP_WRITABLE_1_COLUMN + " = " + ALWAYS_GENERATED_PROP_WRITABLE_1 + " , " + + INSERT_GENERATED_PROP_WRITABLE_1_COLUMN + " = " + INSERT_GENERATED_PROP_WRITABLE_1 + + " where id = NEW.id; return NEW; END; $afterInsert$ LANGUAGE plpgsql" ); + try { + afterInsertFunctionPreparedStatement.execute(); + } + finally { + afterInsertFunctionPreparedStatement.close(); + } + + PreparedStatement afterInsertTriggerPreparedStatement = connection.prepareStatement( + "create or replace trigger after_insert_t after insert on test_entity for each row execute function afterInsert();" ); + try { + afterInsertTriggerPreparedStatement.execute(); + } + finally { + afterInsertTriggerPreparedStatement.close(); + } + } ) + ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( + session -> + session.doWork( connection -> { + + PreparedStatement dropAfterInsertTrigger = connection + .prepareStatement( "drop trigger IF EXISTS after_insert_t on test_entity" ); + try { + dropAfterInsertTrigger.execute(); + } + finally { + dropAfterInsertTrigger.close(); + } + + PreparedStatement dropAfterInsertFunction = connection + .prepareStatement( "drop function IF EXISTS afterInsert" ); + try { + dropAfterInsertFunction.execute(); + } + finally { + dropAfterInsertFunction.close(); + } + + PreparedStatement dropTable = connection.prepareStatement( "drop table test_entity" ); + try { + dropTable.execute(); + } + finally { + dropTable.close(); + } + } ) + ); + } + + @Test + public void testPersistAndFind(SessionFactoryScope scope) { + SQLStatementInspector sqlStatementInspector = (SQLStatementInspector) scope.getStatementInspector(); + sqlStatementInspector.clear(); + + TestEntity testEntity = new TestEntity( + 1, + NON_GENERATED_PROP_0, + new EmbeddableValue( NON_GENERATED_PROP_1 ) + ); + + scope.inTransaction( + session -> + session.persist( testEntity ) + ); + + sqlStatementInspector.assertExecutedCount( 2 ); + sqlStatementInspector.assertIsInsert( 0 ); + sqlStatementInspector.assertIsSelect( 1 ); + + assertThatInsertIsCorrect( sqlStatementInspector.getSqlQueries().get( 0 ) ); + assertThatSelectOnInsertIsCorrect( + sqlStatementInspector.getSqlQueries().get( 1 ), + testEntity + ); + + sqlStatementInspector.clear(); + scope.inTransaction( + session -> { + TestEntity found = session.find( TestEntity.class, 1 ); + + sqlStatementInspector.assertExecutedCount( 1 ); + sqlStatementInspector.assertIsSelect( 0 ); + assertThatSelectStatementIsCorrect( sqlStatementInspector.getSqlQueries().get( 0 ) ); + + sqlStatementInspector.clear(); + + found.setName( UPDATED_NON_GENERATED_PROP_0 ); + found.getEmbeddableValue().setNonGeneratedProperty1( UPDATED_NON_GENERATED_PROP_1 ); + session.flush(); + + sqlStatementInspector.assertExecutedCount( 2 ); + sqlStatementInspector.assertIsUpdate( 0 ); + assertThatUpdateStatementIsCorrect( sqlStatementInspector.getSqlQueries().get( 0 ) ); + assertThatSelectOnUpdateIsCorrect( + sqlStatementInspector.getSqlQueries().get( 1 ), + found + ); + } + ); + } + + private static void assertThatInsertIsCorrect(String insertQuery) { + assertThat( insertQuery ).contains( NON_GENERATED_PROP_0_COLUMN ); + assertThat( insertQuery ).contains( ALWAYS_GENERATED_PROP_WRITABLE_0_COLUMN ); + assertThat( insertQuery ).contains( NON_GENERATED_PROP_1_COLUMN ); + assertThat( insertQuery ).contains( ALWAYS_GENERATED_PROP_WRITABLE_1_COLUMN ); + assertThat( insertQuery ).contains( INSERT_GENERATED_PROP_WRITABLE_1_COLUMN ); + + assertThat( insertQuery ).doesNotContain( ALWAYS_GENERATED_PROP_0_COLUMN ); + assertThat( insertQuery ).doesNotContain( ALWAYS_GENERATED_PROP_1_COLUMN ); + assertThat( insertQuery ).doesNotContain( INSERT_GENERATED_PROP_1_COLUMN ); + } + + private static void assertThatSelectOnInsertIsCorrect(String selectQuery, TestEntity testEntity) { + assertThat( selectQuery ).contains( ALWAYS_GENERATED_PROP_0_COLUMN ); + assertThat( selectQuery ).contains( ALWAYS_GENERATED_PROP_1_COLUMN ); + + assertThat( selectQuery ).contains( ALWAYS_GENERATED_PROP_WRITABLE_0_COLUMN ); + assertThat( selectQuery ).contains( ALWAYS_GENERATED_PROP_WRITABLE_1_COLUMN ); + + assertThat( selectQuery ).contains( INSERT_GENERATED_PROP_1_COLUMN ); + assertThat( selectQuery ).contains( INSERT_GENERATED_PROP_WRITABLE_1_COLUMN ); + + assertThat( selectQuery ).doesNotContain( NON_GENERATED_PROP_0_COLUMN ); +// assertThat( selectQuery ).doesNotContain( NON_GENERATED_PROP_1_COLUMN ); + + assertThat( testEntity.getName() ).isEqualTo( NON_GENERATED_PROP_0 ); + assertThat( testEntity.getAlwaysGeneratedProp0() ).isEqualTo( ALWAYS_GENERATED_PROP_0 ); + assertThat( testEntity.getAlwaysGeneratedProp01() ).isEqualTo( ALWAYS_GENERATED_PROP_WRITABLE_0 ); + + EmbeddableValue embeddableValue = testEntity.getEmbeddableValue(); + assertThat( embeddableValue.getNonGeneratedProperty1() ) + .isEqualTo( NON_GENERATED_PROP_1 ); + assertThat( embeddableValue.getAlwaysGeneratedProperty1() ) + .isEqualTo( ALWAYS_GENERATED_PROP_1 ); + assertThat( embeddableValue.getAlwaysGeneratedProperty11() ) + .isEqualTo( ALWAYS_GENERATED_PROP_WRITABLE_1 ); + assertThat( embeddableValue.getInsertGeneratedProperty1() ) + .isEqualTo( INSERT_GENERATED_PROP_1 ); + assertThat( embeddableValue.getInsertGeneratedProperty11() ) + .isEqualTo( INSERT_GENERATED_PROP_WRITABLE_1 ); + } + + private static void assertThatSelectStatementIsCorrect(String selectQuery) { + assertThat( selectQuery ).contains( NON_GENERATED_PROP_0_COLUMN ); + assertThat( selectQuery ).contains( ALWAYS_GENERATED_PROP_0_COLUMN ); + assertThat( selectQuery ).contains( ALWAYS_GENERATED_PROP_WRITABLE_0_COLUMN ); + assertThat( selectQuery ).contains( NON_GENERATED_PROP_1_COLUMN ); + assertThat( selectQuery ).contains( ALWAYS_GENERATED_PROP_1_COLUMN ); + assertThat( selectQuery ).contains( ALWAYS_GENERATED_PROP_WRITABLE_1_COLUMN ); + assertThat( selectQuery ).contains( INSERT_GENERATED_PROP_1_COLUMN ); + assertThat( selectQuery ).contains( INSERT_GENERATED_PROP_WRITABLE_1_COLUMN ); + } + + private static void assertThatUpdateStatementIsCorrect(String updateQuery) { + assertThat( updateQuery ).contains( NON_GENERATED_PROP_0_COLUMN ); + + assertThat( updateQuery ).contains( ALWAYS_GENERATED_PROP_WRITABLE_0_COLUMN ); + assertThat( updateQuery ).contains( NON_GENERATED_PROP_1_COLUMN ); + assertThat( updateQuery ).contains( ALWAYS_GENERATED_PROP_WRITABLE_1_COLUMN ); + assertThat( updateQuery ).contains( INSERT_GENERATED_PROP_WRITABLE_1_COLUMN ); + + assertThat( updateQuery ).doesNotContain( ALWAYS_GENERATED_PROP_0_COLUMN ); + assertThat( updateQuery ).doesNotContain( ALWAYS_GENERATED_PROP_1_COLUMN ); + assertThat( updateQuery ).doesNotContain( INSERT_GENERATED_PROP_1_COLUMN ); + } + + private static void assertThatSelectOnUpdateIsCorrect(String selectQuery, TestEntity testEntity) { + assertThat( selectQuery ).contains( ALWAYS_GENERATED_PROP_0_COLUMN ); + assertThat( selectQuery ).contains( ALWAYS_GENERATED_PROP_1_COLUMN ); + assertThat( selectQuery ).contains( ALWAYS_GENERATED_PROP_WRITABLE_0_COLUMN ); + assertThat( selectQuery ).contains( ALWAYS_GENERATED_PROP_WRITABLE_1_COLUMN ); + + assertThat( selectQuery ).doesNotContain( NON_GENERATED_PROP_0_COLUMN ); +// assertThat( selectQuery ).doesNotContain( NON_GENERATED_PROP_1_COLUMN ); +// assertThat( selectQuery ).contains( INSERT_GENERATED_PROP_WRITABLE_1_COLUMN ); +// assertThat( selectQuery ).doesNotContain( INSERT_GENERATED_PROP_1_COLUMN ); + + assertThat( testEntity.getName() ).isEqualTo( UPDATED_NON_GENERATED_PROP_0 ); + assertThat( testEntity.getAlwaysGeneratedProp0() ).isEqualTo( ALWAYS_GENERATED_PROP_0 ); + assertThat( testEntity.getAlwaysGeneratedProp01() ).isEqualTo( ALWAYS_GENERATED_PROP_WRITABLE_0 ); + + EmbeddableValue embeddableValue = testEntity.getEmbeddableValue(); + assertThat( embeddableValue.getNonGeneratedProperty1() ) + .isEqualTo( UPDATED_NON_GENERATED_PROP_1 ); + assertThat( embeddableValue.getAlwaysGeneratedProperty1() ) + .isEqualTo( ALWAYS_GENERATED_PROP_1 ); + assertThat( embeddableValue.getAlwaysGeneratedProperty11() ) + .isEqualTo( ALWAYS_GENERATED_PROP_WRITABLE_1 ); + assertThat( embeddableValue.getInsertGeneratedProperty1() ) + .isEqualTo( INSERT_GENERATED_PROP_1 ); + assertThat( embeddableValue.getInsertGeneratedProperty11() ) + .isEqualTo( INSERT_GENERATED_PROP_WRITABLE_1 ); + } + + @Entity(name = "TestEntity") + @Table(name = "test_entity") + public static class TestEntity { + + @Id + private Integer id; + @Column(name = NON_GENERATED_PROP_0_COLUMN) + private String name; + + @Column(name = ALWAYS_GENERATED_PROP_0_COLUMN) + @Generated(GenerationTime.ALWAYS) + private Integer alwaysGeneratedProp0; + + @Column(name = ALWAYS_GENERATED_PROP_WRITABLE_0_COLUMN) + @Generated(value = GenerationTime.ALWAYS, writable = true) + private Integer alwaysGeneratedProp01; + + @Embedded + private EmbeddableValue embeddableValue; + + public TestEntity() { + } + + public TestEntity( + Integer id, + String name, + EmbeddableValue embeddableValue) { + this.id = id; + this.name = name; + this.embeddableValue = embeddableValue; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getAlwaysGeneratedProp0() { + return alwaysGeneratedProp0; + } + + public void setAlwaysGeneratedProp0(Integer alwaysGeneratedProp0) { + this.alwaysGeneratedProp0 = alwaysGeneratedProp0; + } + + public Integer getAlwaysGeneratedProp01() { + return alwaysGeneratedProp01; + } + + public void setAlwaysGeneratedProp01(Integer alwaysGeneratedProp01) { + this.alwaysGeneratedProp01 = alwaysGeneratedProp01; + } + + public EmbeddableValue getEmbeddableValue() { + return embeddableValue; + } + + public void setEmbeddableValue(EmbeddableValue embeddableValue) { + this.embeddableValue = embeddableValue; + } + } + + @Embeddable + public static class EmbeddableValue { + @Column(name = NON_GENERATED_PROP_1_COLUMN) + private Integer nonGeneratedProperty1; + + @Generated(GenerationTime.ALWAYS) + @Column(name = ALWAYS_GENERATED_PROP_1_COLUMN) + private Integer alwaysGeneratedProperty1; + + @Generated(value = GenerationTime.ALWAYS, writable = true) + @Column(name = ALWAYS_GENERATED_PROP_WRITABLE_1_COLUMN) + private Integer alwaysGeneratedProperty11; + + @Generated(GenerationTime.INSERT) + @Column(name = INSERT_GENERATED_PROP_1_COLUMN) + private Integer insertGeneratedProperty1; + + @Generated(value = GenerationTime.INSERT, writable = true) + @Column(name = INSERT_GENERATED_PROP_WRITABLE_1_COLUMN) + private Integer insertGeneratedProperty11; + + public Integer getInsertGeneratedProperty1() { + return insertGeneratedProperty1; + } + + public void setInsertGeneratedProperty1(Integer insertGeneratedProperty1) { + this.insertGeneratedProperty1 = insertGeneratedProperty1; + } + + public Integer getInsertGeneratedProperty11() { + return insertGeneratedProperty11; + } + + public void setInsertGeneratedProperty11(Integer insertGeneratedProperty11) { + this.insertGeneratedProperty11 = insertGeneratedProperty11; + } + + public EmbeddableValue() { + } + + public EmbeddableValue(Integer nonGeneratedProperty1) { + this.nonGeneratedProperty1 = nonGeneratedProperty1; + } + + + public Integer getNonGeneratedProperty1() { + return nonGeneratedProperty1; + } + + public void setNonGeneratedProperty1(Integer nonGeneratedProperty1) { + this.nonGeneratedProperty1 = nonGeneratedProperty1; + } + + public Integer getAlwaysGeneratedProperty1() { + return alwaysGeneratedProperty1; + } + + public void setAlwaysGeneratedProperty1(Integer alwaysGeneratedProperty1) { + this.alwaysGeneratedProperty1 = alwaysGeneratedProperty1; + } + + public Integer getAlwaysGeneratedProperty11() { + return alwaysGeneratedProperty11; + } + + public void setAlwaysGeneratedProperty11(Integer alwaysGeneratedProperty11) { + this.alwaysGeneratedProperty11 = alwaysGeneratedProperty11; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/InsertGeneratedTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/InsertGeneratedTest.java new file mode 100644 index 000000000000..5909e6b05069 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/InsertGeneratedTest.java @@ -0,0 +1,360 @@ +package org.hibernate.orm.test.embeddable.generated; + +import java.sql.PreparedStatement; + +import org.hibernate.annotations.Generated; +import org.hibernate.annotations.GenerationTime; +import org.hibernate.dialect.PostgreSQLDialect; + +import org.hibernate.testing.jdbc.SQLStatementInspector; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.Jira; +import org.hibernate.testing.orm.junit.RequiresDialect; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +@RequiresDialect(PostgreSQLDialect.class) +@DomainModel( + annotatedClasses = { + InsertGeneratedTest.TestEntity.class + } +) +@SessionFactory( + exportSchema = false, + useCollectingStatementInspector = true +) +@Jira("HHH-16957") +public class InsertGeneratedTest { + + private final static String NON_GENERATED_PROP_0 = "a"; + private final static String UPDATED_NON_GENERATED_PROP_0 = "b"; + private final static int NON_GENERATED_PROP_1 = 20; + private final static int UPDATED_NON_GENERATED_PROP_1 = 20; + + private final static int INSERT_GENERATED_PROP_0 = 1; + private final static int INSERT_GENERATED_PROP_WRITABLE_0 = 2; + private final static int INSERT_GENERATED_PROP_1 = 3; + private final static int INSERT_GENERATED_PROP_WRITABLE_1 = 4; + + private final static String NON_GENERATED_PROP_0_COLUMN = "non_generate_prop_0"; + private final static String INSERT_GENERATED_PROP_0_COLUMN = "insert_generated_prop_0"; + private final static String INSERT_GENERATED_PROP_WRITABLE_0_COLUMN = "insert_generated_prop_writable_0"; + + private final static String NON_GENERATED_PROP_1_COLUMN = "non_generate_prop_1"; + private final static String INSERT_GENERATED_PROP_1_COLUMN = "insert_generated_prop_1"; + private final static String INSERT_GENERATED_PROP_WRITABLE_1_COLUMN = "insert_generated_prop_writable_1"; + + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( + session -> + session.doWork( connection -> { + PreparedStatement createTablePreparedStatement = connection.prepareStatement( + "create table IF NOT EXISTS test_entity (" + + " id integer not null, " + + NON_GENERATED_PROP_0_COLUMN + " varchar(255), " + + INSERT_GENERATED_PROP_0_COLUMN + " integer generated always as ( " + INSERT_GENERATED_PROP_0 + " ) stored, " + + INSERT_GENERATED_PROP_WRITABLE_0_COLUMN + " integer, " + + NON_GENERATED_PROP_1_COLUMN + " integer, " + + INSERT_GENERATED_PROP_1_COLUMN + " integer generated always as ( " + INSERT_GENERATED_PROP_1 + " ) stored, " + + INSERT_GENERATED_PROP_WRITABLE_1_COLUMN + " integer, " + + " primary key (id)" + + " )" ); + try { + createTablePreparedStatement.execute(); + } + finally { + createTablePreparedStatement.close(); + } + + PreparedStatement afterInsertFunctionPreparedStatement = connection.prepareStatement( + "create or replace function afterInsert() returns trigger as $afterInsert$ begin " + + "update test_entity set " + + INSERT_GENERATED_PROP_WRITABLE_0_COLUMN + " = " + INSERT_GENERATED_PROP_WRITABLE_0 + " , " + + INSERT_GENERATED_PROP_WRITABLE_1_COLUMN + " = " + INSERT_GENERATED_PROP_WRITABLE_1 + + " where id = NEW.id; return NEW; END; $afterInsert$ LANGUAGE plpgsql" ); + try { + afterInsertFunctionPreparedStatement.execute(); + } + finally { + afterInsertFunctionPreparedStatement.close(); + } + + PreparedStatement afterInsertTriggerPreparedStatement = connection.prepareStatement( + "create or replace trigger after_insert_t after insert on test_entity for each row execute function afterInsert();" ); + try { + afterInsertTriggerPreparedStatement.execute(); + } + finally { + afterInsertTriggerPreparedStatement.close(); + } + } ) + ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( + session -> + session.doWork( connection -> { + + PreparedStatement dropAfterInsertTrigger = connection + .prepareStatement( "drop trigger IF EXISTS after_insert_t on test_entity" ); + try { + dropAfterInsertTrigger.execute(); + } + finally { + dropAfterInsertTrigger.close(); + } + + PreparedStatement dropAfterInsertFunction = connection + .prepareStatement( "drop function IF EXISTS afterInsert" ); + try { + dropAfterInsertFunction.execute(); + } + finally { + dropAfterInsertFunction.close(); + } + + PreparedStatement dropTable = connection.prepareStatement( "drop table test_entity" ); + try { + dropTable.execute(); + } + finally { + dropTable.close(); + } + } ) + ); + } + + @Test + public void testPersistAndFind(SessionFactoryScope scope) { + SQLStatementInspector sqlStatementInspector = (SQLStatementInspector) scope.getStatementInspector(); + sqlStatementInspector.clear(); + + TestEntity testEntity = new TestEntity( + 1, + NON_GENERATED_PROP_0, + new EmbeddableValue( NON_GENERATED_PROP_1 ) + ); + + scope.inTransaction( + session -> + session.persist( testEntity ) + ); + + sqlStatementInspector.assertExecutedCount( 2 ); + sqlStatementInspector.assertIsInsert( 0 ); + sqlStatementInspector.assertIsSelect( 1 ); + + assertThatInsertIsCorrect( sqlStatementInspector.getSqlQueries().get( 0 ) ); + assertThatSelectOnInsertIsCorrect( + sqlStatementInspector.getSqlQueries().get( 1 ), + testEntity + ); + + sqlStatementInspector.clear(); + scope.inTransaction( + session -> { + TestEntity found = session.find( TestEntity.class, 1 ); + + sqlStatementInspector.assertExecutedCount( 1 ); + sqlStatementInspector.assertIsSelect( 0 ); + assertThatSelectStatementIsCorrect( sqlStatementInspector.getSqlQueries().get( 0 ) ); + + sqlStatementInspector.clear(); + + found.setName( UPDATED_NON_GENERATED_PROP_0 ); + found.getEmbeddableValue().setNonGeneratedProperty1( UPDATED_NON_GENERATED_PROP_1 ); + session.flush(); + + sqlStatementInspector.assertExecutedCount( 1 ); + sqlStatementInspector.assertIsUpdate( 0 ); + assertThatUpdateStatementIsCorrect( sqlStatementInspector.getSqlQueries().get( 0 ) ); + } + ); + } + + private static void assertThatInsertIsCorrect(String insertQuery) { + assertThat( insertQuery ).contains( NON_GENERATED_PROP_0_COLUMN ); + assertThat( insertQuery ).contains( INSERT_GENERATED_PROP_WRITABLE_0_COLUMN ); + assertThat( insertQuery ).contains( NON_GENERATED_PROP_1_COLUMN ); + assertThat( insertQuery ).contains( INSERT_GENERATED_PROP_WRITABLE_1_COLUMN ); + + assertThat( insertQuery ).doesNotContain( INSERT_GENERATED_PROP_0_COLUMN ); + assertThat( insertQuery ).doesNotContain( INSERT_GENERATED_PROP_1_COLUMN ); + } + + private static void assertThatSelectOnInsertIsCorrect(String selectQuery, TestEntity testEntity) { + assertThat( selectQuery ).contains( INSERT_GENERATED_PROP_0_COLUMN ); + assertThat( selectQuery ).contains( INSERT_GENERATED_PROP_1_COLUMN ); + assertThat( selectQuery ).contains( INSERT_GENERATED_PROP_WRITABLE_0_COLUMN ); + assertThat( selectQuery ).contains( INSERT_GENERATED_PROP_WRITABLE_1_COLUMN ); + + assertThat( selectQuery ).doesNotContain( NON_GENERATED_PROP_0_COLUMN ); +// assertThat( selectQuery ).doesNotContain( NON_GENERATED_PROP_1_COLUMN ); + + assertThat( testEntity.getName() ).isEqualTo( NON_GENERATED_PROP_0 ); + assertThat( testEntity.getInsertGeneratedProp0() ).isEqualTo( INSERT_GENERATED_PROP_0 ); + assertThat( testEntity.getInsertGeneratedProp01() ).isEqualTo( INSERT_GENERATED_PROP_WRITABLE_0 ); + + EmbeddableValue embeddableValue = testEntity.getEmbeddableValue(); + assertThat( embeddableValue.getNonGeneratedProperty1() ) + .isEqualTo( NON_GENERATED_PROP_1 ); + assertThat( embeddableValue.getInsertGeneratedProperty1() ) + .isEqualTo( INSERT_GENERATED_PROP_1 ); + assertThat( embeddableValue.getInsertGeneratedProperty11() ) + .isEqualTo( INSERT_GENERATED_PROP_WRITABLE_1 ); + } + + private static void assertThatSelectStatementIsCorrect(String selectQuery) { + assertThat( selectQuery ).contains( NON_GENERATED_PROP_0_COLUMN ); + assertThat( selectQuery ).contains( INSERT_GENERATED_PROP_0_COLUMN ); + assertThat( selectQuery ).contains( INSERT_GENERATED_PROP_WRITABLE_0_COLUMN ); + assertThat( selectQuery ).contains( NON_GENERATED_PROP_1_COLUMN ); + assertThat( selectQuery ).contains( INSERT_GENERATED_PROP_1_COLUMN ); + assertThat( selectQuery ).contains( INSERT_GENERATED_PROP_WRITABLE_1_COLUMN ); + } + + private static void assertThatUpdateStatementIsCorrect(String updateQuery) { + assertThat( updateQuery ).contains( NON_GENERATED_PROP_0_COLUMN ); + assertThat( updateQuery ).contains( NON_GENERATED_PROP_1_COLUMN ); + + assertThat( updateQuery ).contains( INSERT_GENERATED_PROP_WRITABLE_0_COLUMN ); + assertThat( updateQuery ).contains( INSERT_GENERATED_PROP_WRITABLE_1_COLUMN ); + + assertThat( updateQuery ).doesNotContain( INSERT_GENERATED_PROP_0_COLUMN ); + assertThat( updateQuery ).doesNotContain( INSERT_GENERATED_PROP_1_COLUMN ); + } + + @Entity(name = "TestEntity") + @Table(name = "test_entity") + public static class TestEntity { + + @Id + private Integer id; + @Column(name = NON_GENERATED_PROP_0_COLUMN) + private String name; + + @Column(name = INSERT_GENERATED_PROP_0_COLUMN) + @Generated(GenerationTime.INSERT) + private Integer insertGeneratedProp0; + + @Column(name = INSERT_GENERATED_PROP_WRITABLE_0_COLUMN) + @Generated(value = GenerationTime.INSERT, writable = true) + private Integer insertGeneratedProp01; + + @Embedded + private EmbeddableValue embeddableValue; + + public TestEntity() { + } + + public TestEntity( + Integer id, + String name, + EmbeddableValue embeddableValue) { + this.id = id; + this.name = name; + this.embeddableValue = embeddableValue; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getInsertGeneratedProp0() { + return insertGeneratedProp0; + } + + public void setInsertGeneratedProp0(Integer insertGeneratedProp0) { + this.insertGeneratedProp0 = insertGeneratedProp0; + } + + public Integer getInsertGeneratedProp01() { + return insertGeneratedProp01; + } + + public void setInsertGeneratedProp01(Integer insertGeneratedProp01) { + this.insertGeneratedProp01 = insertGeneratedProp01; + } + + public EmbeddableValue getEmbeddableValue() { + return embeddableValue; + } + + public void setEmbeddableValue(EmbeddableValue embeddableValue) { + this.embeddableValue = embeddableValue; + } + } + + @Embeddable + public static class EmbeddableValue { + @Column(name = NON_GENERATED_PROP_1_COLUMN) + private Integer nonGeneratedProperty1; + + @Generated(GenerationTime.INSERT) + @Column(name = INSERT_GENERATED_PROP_1_COLUMN) + private Integer insertGeneratedProperty1; + + @Generated(value = GenerationTime.INSERT, writable = true) + @Column(name = INSERT_GENERATED_PROP_WRITABLE_1_COLUMN) + private Integer insertGeneratedProperty11; + + public EmbeddableValue() { + } + + public EmbeddableValue(Integer nonGeneratedProperty1) { + this.nonGeneratedProperty1 = nonGeneratedProperty1; + } + + + public Integer getNonGeneratedProperty1() { + return nonGeneratedProperty1; + } + + public void setNonGeneratedProperty1(Integer nonGeneratedProperty1) { + this.nonGeneratedProperty1 = nonGeneratedProperty1; + } + + public Integer getInsertGeneratedProperty1() { + return insertGeneratedProperty1; + } + + public void setInsertGeneratedProperty1(Integer insertGeneratedProperty1) { + this.insertGeneratedProperty1 = insertGeneratedProperty1; + } + + public Integer getInsertGeneratedProperty11() { + return insertGeneratedProperty11; + } + + public void setInsertGeneratedProperty11(Integer insertGeneratedProperty11) { + this.insertGeneratedProperty11 = insertGeneratedProperty11; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/NestedEmbeddableTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/NestedEmbeddableTest.java new file mode 100644 index 000000000000..183420b77403 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/NestedEmbeddableTest.java @@ -0,0 +1,89 @@ +package org.hibernate.orm.test.embeddable.generated; + +import java.util.HashSet; +import java.util.Set; + +import org.hibernate.annotations.Generated; +import org.hibernate.annotations.GenerationTime; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.CollectionTable; +import jakarta.persistence.ElementCollection; +import jakarta.persistence.Embeddable; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; + +@DomainModel( + annotatedClasses = { + NestedEmbeddableTest.TestEntity.class + } +) +@SessionFactory +public class NestedEmbeddableTest { + + @Test + public void testEmbeddable(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + TestEntity testEntity = new TestEntity( 1l, "prop1", "prop2" ); + session.save( testEntity ); + } + ); + } + + @Entity(name = "TestEntity") + public static class TestEntity { + @Id + private Long id; + + @Embedded + private AnEmbeddable anEmbeddable; + + @ElementCollection + @CollectionTable(name = "EmbSetEnt_set") + private Set componentSet = new HashSet<>(); + + public TestEntity() { + } + + public TestEntity(Long id, String property1, String property2) { + this.id = id; + this.anEmbeddable = new AnEmbeddable( property1, property2 ); + } + } + + @Embeddable + public static class AnEmbeddable { + @Generated(GenerationTime.INSERT) + private String property1; + + @Embedded + private AnotherEmbeddable anotherEmbeddable; + + public AnEmbeddable() { + } + + public AnEmbeddable(String property1, String property2) { + this.property1 = property1; + this.anotherEmbeddable = new AnotherEmbeddable( property2 ); + } + } + + @Embeddable + public static class AnotherEmbeddable { + @Generated(GenerationTime.UPDATE) + private String property2; + + public AnotherEmbeddable() { + } + + public AnotherEmbeddable(String property2) { + this.property2 = property2; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/UpdateGeneratedTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/UpdateGeneratedTest.java new file mode 100644 index 000000000000..7b35c24c3e52 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/UpdateGeneratedTest.java @@ -0,0 +1,361 @@ +package org.hibernate.orm.test.embeddable.generated; + +import java.sql.PreparedStatement; + +import org.hibernate.annotations.Generated; +import org.hibernate.annotations.GenerationTime; +import org.hibernate.dialect.PostgreSQLDialect; + +import org.hibernate.testing.jdbc.SQLStatementInspector; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.Jira; +import org.hibernate.testing.orm.junit.RequiresDialect; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +@RequiresDialect(PostgreSQLDialect.class) +@DomainModel( + annotatedClasses = { + UpdateGeneratedTest.TestEntity.class + } +) +@SessionFactory( + exportSchema = false, + useCollectingStatementInspector = true +) +@Jira("HHH-16957") +public class UpdateGeneratedTest { + private final static String NON_GENERATED_PROP_0 = "a"; + private final static String UPDATED_NON_GENERATED_PROP_0 = "b"; + private final static int NON_GENERATED_PROP_1 = 20; + private final static int UPDATED_NON_GENERATED_PROP_1 = 20; + + private final static int UPDATE_GENERATED_PROP_0 = 1; + private final static int UPDATE_GENERATED_PROP_WRITABLE_0 = 2; + private final static int UPDATE_GENERATED_PROP_1 = 3; + private final static int UPDATE_GENERATED_PROP_WRITABLE_1 = 4; + + private final static String NON_GENERATED_PROP_0_COLUMN = "non_generate_prop_0"; + private final static String UPDATE_GENERATED_PROP_0_COLUMN = "update_generated_prop_0"; + private final static String UPDATE_GENERATED_PROP_WRITABLE_0_COLUMN = "update_generated_prop_writable_0"; + + private final static String NON_GENERATED_PROP_1_COLUMN = "non_generate_prop_1"; + private final static String UPDATE_GENERATED_PROP_1_COLUMN = "update_generated_prop_1"; + private final static String UPDATE_GENERATED_PROP_WRITABLE_1_COLUMN = "update_generated_prop_writable_1"; + + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( + session -> + session.doWork( connection -> { + PreparedStatement createTablePreparedStatement = connection.prepareStatement( + "create table IF NOT EXISTS test_entity (" + + " id integer not null, " + + NON_GENERATED_PROP_0_COLUMN + " varchar(255), " + + UPDATE_GENERATED_PROP_0_COLUMN + " integer , " + + UPDATE_GENERATED_PROP_WRITABLE_0_COLUMN + " integer, " + + NON_GENERATED_PROP_1_COLUMN + " integer, " + + UPDATE_GENERATED_PROP_1_COLUMN + " integer , " + + UPDATE_GENERATED_PROP_WRITABLE_1_COLUMN + " integer, " + + " primary key (id)" + + " )" ); + try { + createTablePreparedStatement.execute(); + } + finally { + createTablePreparedStatement.close(); + } + + PreparedStatement afterUpdateFunctionPreparedStatement = connection.prepareStatement( + "create or replace function afterUpdate() returns trigger as $afterUpdate$ begin " + + "update test_entity set " + + UPDATE_GENERATED_PROP_WRITABLE_0_COLUMN + " = " + UPDATE_GENERATED_PROP_WRITABLE_0 + " , " + + UPDATE_GENERATED_PROP_0_COLUMN + " = " + UPDATE_GENERATED_PROP_0 + " , " + + UPDATE_GENERATED_PROP_WRITABLE_1_COLUMN + " = " + UPDATE_GENERATED_PROP_WRITABLE_1 + " , " + + UPDATE_GENERATED_PROP_1_COLUMN + " = " + UPDATE_GENERATED_PROP_1 + + " where id = NEW.id and NEW." + UPDATE_GENERATED_PROP_WRITABLE_0_COLUMN + " is null ; " + + "return NEW; END; $afterUpdate$ LANGUAGE plpgsql" ); + try { + afterUpdateFunctionPreparedStatement.execute(); + } + finally { + afterUpdateFunctionPreparedStatement.close(); + } + + PreparedStatement afterUpdateTriggerPreparedStatement = connection.prepareStatement( + "create or replace trigger after_update_t after update on test_entity for each row execute function afterUpdate();" ); + try { + afterUpdateTriggerPreparedStatement.execute(); + } + finally { + afterUpdateTriggerPreparedStatement.close(); + } + } ) + ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( + session -> + session.doWork( connection -> { + + PreparedStatement dropAfterInsertTrigger = connection + .prepareStatement( "drop trigger IF EXISTS after_update_t on test_entity" ); + try { + dropAfterInsertTrigger.execute(); + } + finally { + dropAfterInsertTrigger.close(); + } + + PreparedStatement dropAfterInsertFunction = connection + .prepareStatement( "drop function IF EXISTS afterUpdate" ); + try { + dropAfterInsertFunction.execute(); + } + finally { + dropAfterInsertFunction.close(); + } + + PreparedStatement dropTable = connection.prepareStatement( "drop table test_entity" ); + try { + dropTable.execute(); + } + finally { + dropTable.close(); + } + } ) + ); + } + + @Test + public void testPersistAndFind(SessionFactoryScope scope) { + SQLStatementInspector sqlStatementInspector = (SQLStatementInspector) scope.getStatementInspector(); + sqlStatementInspector.clear(); + + TestEntity testEntity = new TestEntity( + 1, + NON_GENERATED_PROP_0, + new EmbeddableValue( NON_GENERATED_PROP_1 ) + ); + + scope.inTransaction( + session -> + session.persist( testEntity ) + ); + + sqlStatementInspector.assertExecutedCount( 1 ); + sqlStatementInspector.assertIsInsert( 0 ); + + assertThatInsertIsCorrect( sqlStatementInspector.getSqlQueries().get( 0 ) ); + + sqlStatementInspector.clear(); + scope.inTransaction( + session -> { + TestEntity found = session.find( TestEntity.class, 1 ); + + sqlStatementInspector.assertExecutedCount( 1 ); + sqlStatementInspector.assertIsSelect( 0 ); + assertThatSelectStatementIsCorrect( sqlStatementInspector.getSqlQueries().get( 0 ) ); + + sqlStatementInspector.clear(); + + found.setName( UPDATED_NON_GENERATED_PROP_0 ); + found.getEmbeddableValue().setNonGeneratedProperty1( UPDATED_NON_GENERATED_PROP_1 ); + session.flush(); + + sqlStatementInspector.assertExecutedCount( 2 ); + sqlStatementInspector.assertIsUpdate( 0 ); + assertThatUpdateStatementIsCorrect( sqlStatementInspector.getSqlQueries().get( 0 ) ); + assertThatSelectOnUpdateIsCorrect( + sqlStatementInspector.getSqlQueries().get( 1 ), + found + ); + } + ); + } + + private static void assertThatInsertIsCorrect(String insertQuery) { + assertThat( insertQuery ).contains( NON_GENERATED_PROP_0_COLUMN ); + assertThat( insertQuery ).contains( UPDATE_GENERATED_PROP_WRITABLE_0_COLUMN ); + assertThat( insertQuery ).contains( NON_GENERATED_PROP_1_COLUMN ); + assertThat( insertQuery ).contains( UPDATE_GENERATED_PROP_WRITABLE_1_COLUMN ); + + assertThat( insertQuery ).doesNotContain( UPDATE_GENERATED_PROP_0_COLUMN ); + assertThat( insertQuery ).doesNotContain( UPDATE_GENERATED_PROP_1_COLUMN ); + } + + private static void assertThatSelectStatementIsCorrect(String selectQuery) { + assertThat( selectQuery ).contains( NON_GENERATED_PROP_0_COLUMN ); + assertThat( selectQuery ).contains( UPDATE_GENERATED_PROP_0_COLUMN ); + assertThat( selectQuery ).contains( UPDATE_GENERATED_PROP_WRITABLE_0_COLUMN ); + assertThat( selectQuery ).contains( NON_GENERATED_PROP_1_COLUMN ); + assertThat( selectQuery ).contains( UPDATE_GENERATED_PROP_1_COLUMN ); + assertThat( selectQuery ).contains( UPDATE_GENERATED_PROP_WRITABLE_1_COLUMN ); + } + + private static void assertThatUpdateStatementIsCorrect(String updateQuery) { + assertThat( updateQuery ).contains( NON_GENERATED_PROP_0_COLUMN ); + + assertThat( updateQuery ).contains( UPDATE_GENERATED_PROP_WRITABLE_0_COLUMN ); + assertThat( updateQuery ).contains( NON_GENERATED_PROP_1_COLUMN ); + assertThat( updateQuery ).contains( UPDATE_GENERATED_PROP_WRITABLE_1_COLUMN ); + + assertThat( updateQuery ).doesNotContain( UPDATE_GENERATED_PROP_0_COLUMN ); + assertThat( updateQuery ).doesNotContain( UPDATE_GENERATED_PROP_1_COLUMN ); + } + + private static void assertThatSelectOnUpdateIsCorrect(String selectQuery, TestEntity testEntity) { + assertThat( selectQuery ).contains( UPDATE_GENERATED_PROP_0_COLUMN ); + assertThat( selectQuery ).contains( UPDATE_GENERATED_PROP_1_COLUMN ); + assertThat( selectQuery ).contains( UPDATE_GENERATED_PROP_WRITABLE_0_COLUMN ); + assertThat( selectQuery ).contains( UPDATE_GENERATED_PROP_WRITABLE_1_COLUMN ); + + assertThat( selectQuery ).doesNotContain( NON_GENERATED_PROP_0_COLUMN ); +// assertThat( selectQuery ).doesNotContain( NON_GENERATED_PROP_1_COLUMN ); + + assertThat( testEntity.getName() ).isEqualTo( UPDATED_NON_GENERATED_PROP_0 ); + assertThat( testEntity.getUpdateGeneratedProp0() ).isEqualTo( UPDATE_GENERATED_PROP_0 ); + assertThat( testEntity.getUpdateGeneratedProp01() ).isEqualTo( UPDATE_GENERATED_PROP_WRITABLE_0 ); + + EmbeddableValue embeddableValue = testEntity.getEmbeddableValue(); + assertThat( embeddableValue.getNonGeneratedProperty1() ) + .isEqualTo( UPDATED_NON_GENERATED_PROP_1 ); + assertThat( embeddableValue.getUpdateGeneratedProperty1() ) + .isEqualTo( UPDATE_GENERATED_PROP_1 ); + assertThat( embeddableValue.getUpdateGeneratedProperty11() ) + .isEqualTo( UPDATE_GENERATED_PROP_WRITABLE_1 ); + } + + @Entity(name = "TestEntity") + @Table(name = "test_entity") + public static class TestEntity { + + @Id + private Integer id; + @Column(name = NON_GENERATED_PROP_0_COLUMN) + private String name; + + @Column(name = UPDATE_GENERATED_PROP_0_COLUMN) + @Generated(GenerationTime.UPDATE) + private Integer updateGeneratedProp0; + + @Column(name = UPDATE_GENERATED_PROP_WRITABLE_0_COLUMN) + @Generated(value = GenerationTime.UPDATE, writable = true) + private Integer updateGeneratedProp01; + + @Embedded + private EmbeddableValue embeddableValue; + + public TestEntity() { + } + + public TestEntity( + Integer id, + String name, + EmbeddableValue embeddableValue) { + this.id = id; + this.name = name; + this.embeddableValue = embeddableValue; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getUpdateGeneratedProp0() { + return updateGeneratedProp0; + } + + public void setUpdateGeneratedProp0(Integer updateGeneratedProp0) { + this.updateGeneratedProp0 = updateGeneratedProp0; + } + + public Integer getUpdateGeneratedProp01() { + return updateGeneratedProp01; + } + + public void setUpdateGeneratedProp01(Integer updateGeneratedProp01) { + this.updateGeneratedProp01 = updateGeneratedProp01; + } + + public EmbeddableValue getEmbeddableValue() { + return embeddableValue; + } + + public void setEmbeddableValue(EmbeddableValue embeddableValue) { + this.embeddableValue = embeddableValue; + } + } + + @Embeddable + public static class EmbeddableValue { + @Column(name = NON_GENERATED_PROP_1_COLUMN) + private Integer nonGeneratedProperty1; + + @Generated(GenerationTime.UPDATE) + @Column(name = UPDATE_GENERATED_PROP_1_COLUMN) + private Integer updateGeneratedProperty1; + + @Generated(value = GenerationTime.UPDATE, writable = true) + @Column(name = UPDATE_GENERATED_PROP_WRITABLE_1_COLUMN) + private Integer updateGeneratedProperty11; + + public EmbeddableValue() { + } + + public EmbeddableValue(Integer nonGeneratedProperty1) { + this.nonGeneratedProperty1 = nonGeneratedProperty1; + } + + + public Integer getNonGeneratedProperty1() { + return nonGeneratedProperty1; + } + + public void setNonGeneratedProperty1(Integer nonGeneratedProperty1) { + this.nonGeneratedProperty1 = nonGeneratedProperty1; + } + + public Integer getUpdateGeneratedProperty1() { + return updateGeneratedProperty1; + } + + public void setUpdateGeneratedProperty1(Integer updateGeneratedProperty1) { + this.updateGeneratedProperty1 = updateGeneratedProperty1; + } + + public Integer getUpdateGeneratedProperty11() { + return updateGeneratedProperty11; + } + + public void setUpdateGeneratedProperty11(Integer updateGeneratedProperty11) { + this.updateGeneratedProperty11 = updateGeneratedProperty11; + } + } +}