diff --git a/hibernate-core/src/main/java/org/hibernate/generator/CompositeOnExecutionGenerator.java b/hibernate-core/src/main/java/org/hibernate/generator/CompositeOnExecutionGenerator.java new file mode 100644 index 000000000000..24ffff6f7b13 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/generator/CompositeOnExecutionGenerator.java @@ -0,0 +1,11 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.generator; + +public interface CompositeOnExecutionGenerator extends OnExecutionGenerator { + OnExecutionGenerator getPropertyGenerator(String propertyName); + boolean[] writePropertyValues(); + +} diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractSingularAttributeMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractSingularAttributeMapping.java index dfed025f2781..c24f0dfbabcf 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractSingularAttributeMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractSingularAttributeMapping.java @@ -20,6 +20,8 @@ public abstract class AbstractSingularAttributeMapping extends AbstractStateArrayContributorMapping implements SingularAttributeMapping { + private Generator generator; + public AbstractSingularAttributeMapping( String name, int stateArrayPosition, @@ -52,7 +54,20 @@ protected AbstractSingularAttributeMapping( AbstractSingularAttributeMapping ori @Override public Generator getGenerator() { - return findContainingEntityMapping().getEntityPersister().getEntityMetamodel().getGenerators()[getStateArrayPosition()]; + if ( generator != null ) { + return generator; + } + final int stateArrayPosition = getStateArrayPosition(); + if ( stateArrayPosition < 0 ) { + return null; + } + final Generator[] generators = findContainingEntityMapping().getEntityPersister().getEntityMetamodel() + .getGenerators(); + if ( generators.length == 0 ) { + return null; + } + generator = generators[stateArrayPosition]; + return generator; } } 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 ae07f819407f..368332be3b17 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 @@ -24,6 +24,9 @@ import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.CascadeStyle; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.generator.CompositeOnExecutionGenerator; +import org.hibernate.generator.Generator; +import org.hibernate.generator.OnExecutionGenerator; import org.hibernate.mapping.AggregateColumn; import org.hibernate.mapping.Any; import org.hibernate.mapping.BasicValue; @@ -171,6 +174,7 @@ public static EmbeddableMappingTypeImpl from( private final boolean aggregateMappingRequiresColumnWriter; private final boolean preferSelectAggregateMapping; private final boolean preferBindAggregateMapping; + private final CompositeOnExecutionGenerator generator; private EmbeddableMappingTypeImpl( Component bootDescriptor, @@ -251,6 +255,7 @@ private EmbeddableMappingTypeImpl( this.preferSelectAggregateMapping = false; this.preferBindAggregateMapping = false; } + this.generator = resolveOnExecutionGenerator( valueMapping ); } private JdbcMapping resolveJdbcMapping(Component bootDescriptor, RuntimeModelCreationContext creationContext) { @@ -371,6 +376,7 @@ public EmbeddableMappingTypeImpl( attributeMappings ) ); + this.generator = resolveOnExecutionGenerator( valueMapping ); } public EmbeddableMappingType createInverseMappingType( @@ -436,13 +442,17 @@ 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 String name = bootPropertyDescriptor.getName(); if ( subtype instanceof BasicType ) { + boolean isInsertable = isInsertable( generator, insertability[columnPosition], name ); + boolean isUpdatable = isUpdatable( generator, updateability[columnPosition], name ); + final BasicValue basicValue = (BasicValue) value; final Selectable selectable = dependantValue != null ? dependantValue.getColumns().get( dependantColumnIndex + columnPosition ) : @@ -474,7 +484,7 @@ private boolean finishInitialization( containingTableExpression = rootTableExpression; columnExpression = rootTableKeyColumnNames[columnPosition]; } - final NavigableRole role = valueMapping.getNavigableRole().append( bootPropertyDescriptor.getName() ); + final NavigableRole role = valueMapping.getNavigableRole().append( name ); final SelectablePath selectablePath; final String columnDefinition; final Long length; @@ -502,10 +512,10 @@ private boolean finishInitialization( temporalPrecision = null; isLob = false; nullable = bootPropertyDescriptor.isOptional(); - selectablePath = new SelectablePath( determineEmbeddablePrefix() + bootPropertyDescriptor.getName() ); + selectablePath = new SelectablePath( determineEmbeddablePrefix() + name ); } attributeMapping = MappingModelCreationHelper.buildBasicAttributeMapping( - bootPropertyDescriptor.getName(), + name, role, attributeIndex, attributeIndex, @@ -525,8 +535,8 @@ private boolean finishInitialization( temporalPrecision, isLob, nullable, - insertability[columnPosition], - updateability[columnPosition], + isInsertable, + isUpdatable, representationStrategy.resolvePropertyAccess( bootPropertyDescriptor ), compositeType.getCascadeStyle( attributeIndex ), creationProcess @@ -556,7 +566,7 @@ else if ( subtype instanceof AnyType anyType ) { ); attributeMapping = new DiscriminatedAssociationAttributeMapping( - valueMapping.getNavigableRole().append( bootPropertyDescriptor.getName() ), + valueMapping.getNavigableRole().append( name ), typeConfiguration.getJavaTypeRegistry().getDescriptor( Object.class ), this, attributeIndex, @@ -585,7 +595,7 @@ else if ( subtype instanceof CompositeType subCompositeType ) { } attributeMapping = MappingModelCreationHelper.buildEmbeddedAttributeMapping( - bootPropertyDescriptor.getName(), + name, attributeIndex, attributeIndex, bootPropertyDescriptor, @@ -604,7 +614,7 @@ else if ( subtype instanceof CompositeType subCompositeType ) { } else if ( subtype instanceof CollectionType ) { attributeMapping = MappingModelCreationHelper.buildPluralAttributeMapping( - bootPropertyDescriptor.getName(), + name, attributeIndex, attributeIndex, bootPropertyDescriptor, @@ -619,8 +629,8 @@ else if ( subtype instanceof EntityType ) { final EntityPersister entityPersister = creationProcess.getEntityPersister( bootDescriptor.getOwner().getEntityName() ); attributeMapping = MappingModelCreationHelper.buildSingularAssociationAttributeMapping( - bootPropertyDescriptor.getName(), - valueMapping.getNavigableRole().append( bootPropertyDescriptor.getName() ), + name, + valueMapping.getNavigableRole().append( name ), attributeIndex, attributeIndex, bootPropertyDescriptor, @@ -639,7 +649,7 @@ else if ( subtype instanceof EntityType ) { Locale.ROOT, "Unable to determine attribute nature : %s#%s", bootDescriptor.getOwner().getEntityName(), - bootPropertyDescriptor.getName() + name ) ); } @@ -667,6 +677,30 @@ else if ( subtype instanceof EntityType ) { return true; } + private boolean isInsertable(CompositeOnExecutionGenerator generator, boolean propertyInsertability, String propertyName) { + if ( propertyInsertability && generator != null ) { + final OnExecutionGenerator propertyGenerator = generator.getPropertyGenerator( propertyName ); + if ( propertyGenerator != null + && propertyGenerator.generatesOnInsert() + && !propertyGenerator.writePropertyValue() ) { + return false; + } + } + return propertyInsertability; + } + + private boolean isUpdatable(CompositeOnExecutionGenerator generator, boolean propertyUpdatability, String propertyName) { + if ( propertyUpdatability && generator != null ) { + final OnExecutionGenerator propertyGenerator = generator.getPropertyGenerator( propertyName ); + if ( propertyGenerator != null + && propertyGenerator.generatesOnUpdate() + && !propertyGenerator.writePropertyValue() ) { + return false; + } + } + return propertyUpdatability; + } + private boolean isDefinedInClassOrSuperclass(Component bootDescriptor, String declaringClass, String subclass) { while ( subclass != null ) { if ( declaringClass.equals( subclass ) ) { @@ -1121,4 +1155,25 @@ public boolean shouldSelectAggregateMapping() { public boolean shouldBindAggregateMapping() { return preferBindAggregateMapping; } + + private static CompositeOnExecutionGenerator resolveOnExecutionGenerator(EmbeddableValuedModelPart valueMapping) { + if ( valueMapping instanceof EmbeddedAttributeMapping attributeMapping ) { + if ( attributeMapping.getDeclaringType() instanceof EmbeddableMappingTypeImpl embeddableMappingType ) { + if ( embeddableMappingType.getGenerator() instanceof CompositeOnExecutionGenerator compositeOnExecutionGenerator ) { + if ( compositeOnExecutionGenerator.getPropertyGenerator( attributeMapping.getAttributeName() ) + instanceof CompositeOnExecutionGenerator composite ) { + return composite; + } + } + } + else if ( attributeMapping.getGenerator() instanceof CompositeOnExecutionGenerator compositeOnExecutionGenerator ) { + return compositeOnExecutionGenerator; + } + } + return null; + } + + public Generator getGenerator() { + return generator; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedAttributeMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedAttributeMapping.java index 70447085890c..2930dcf3e11a 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedAttributeMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedAttributeMapping.java @@ -408,6 +408,11 @@ public boolean containsTableReference(String tableExpression) { return producer.containsTableReference( tableExpression ); } + @Override + public boolean isEntityIdentifierMapping() { + return getAttributeMetadata() instanceof EmbeddedIdentifierMappingImpl; + } + @Override public int compare(Object value1, Object value2) { return embeddableMappingType.compare( value1, value2 ); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/InverseNonAggregatedIdentifierMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/InverseNonAggregatedIdentifierMapping.java index bb0b0c74d52a..117e495ac8c5 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/InverseNonAggregatedIdentifierMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/InverseNonAggregatedIdentifierMapping.java @@ -13,6 +13,7 @@ import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.event.spi.MergeContext; +import org.hibernate.generator.Generator; import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.metamodel.mapping.AttributeMapping; import org.hibernate.metamodel.mapping.EmbeddableMappingType; @@ -332,4 +333,9 @@ public int getNumberOfFetchables() { public Fetchable getFetchable(int position) { return getPartMappingType().getFetchable( position ); } + + @Override + public Generator getGenerator() { + return null; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/VirtualEmbeddedAttributeMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/VirtualEmbeddedAttributeMapping.java index e38ebab4dcbf..3063393a2b33 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/VirtualEmbeddedAttributeMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/VirtualEmbeddedAttributeMapping.java @@ -6,6 +6,7 @@ import org.hibernate.engine.FetchStyle; import org.hibernate.engine.FetchTiming; +import org.hibernate.generator.Generator; import org.hibernate.metamodel.mapping.AttributeMetadata; import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart; @@ -97,4 +98,8 @@ public VirtualEmbeddedAttributeMapping( ); } + @Override + public Generator getGenerator() { + return null; + } } 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 386f5f58de05..dd9820dcb99f 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 @@ -14,6 +14,7 @@ import org.hibernate.engine.jdbc.mutation.spi.MutationExecutorService; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.generator.CompositeOnExecutionGenerator; import org.hibernate.generator.OnExecutionGenerator; import org.hibernate.metamodel.mapping.AttributeMapping; import org.hibernate.metamodel.mapping.AttributeMappingsList; @@ -116,22 +117,101 @@ protected MutationOperationGroup createOperationGroup(ValuesAnalysis valuesAnaly } } - protected void handleValueGeneration( + protected void handleInsertableValueGeneration( AttributeMapping attributeMapping, MutationGroupBuilder mutationGroupBuilder, OnExecutionGenerator generator) { + if ( generator instanceof CompositeOnExecutionGenerator compositeOnExecutionGenerator ) { + handleInsertableValueGeneration( attributeMapping, mutationGroupBuilder, compositeOnExecutionGenerator ); + } + else { + final Dialect dialect = factory.getJdbcServices().getDialect(); + final boolean writePropertyValue = generator.writePropertyValue(); + final String[] columnValues = writePropertyValue ? null : generator.getReferencedColumnValues( dialect ); + attributeMapping.forEachSelectable( (j, mapping) -> { + if ( mapping.isInsertable() ) { + final String tableName = entityPersister.physicalTableNameForMutation( mapping ); + final ColumnValuesTableMutationBuilder tableUpdateBuilder = mutationGroupBuilder.findTableDetailsBuilder( + tableName ); + tableUpdateBuilder.addValueColumn( + mapping.getSelectionExpression(), + writePropertyValue ? "?" : columnValues[j], + mapping.getJdbcMapping(), + mapping.isLob() + ); + } + } ); + } + } + + protected void handleInsertableValueGeneration( + AttributeMapping attributeMapping, + MutationGroupBuilder mutationGroupBuilder, + CompositeOnExecutionGenerator generator) { final Dialect dialect = factory.getJdbcServices().getDialect(); - final boolean writePropertyValue = generator.writePropertyValue(); - final String[] columnValues = writePropertyValue ? null : generator.getReferencedColumnValues( dialect ); + final boolean[] writePropertyValue = generator.writePropertyValues(); + final String[] columnValues = generator.getReferencedColumnValues( dialect ); attributeMapping.forEachSelectable( (j, mapping) -> { - final String tableName = entityPersister.physicalTableNameForMutation( mapping ); - final ColumnValuesTableMutationBuilder tableUpdateBuilder = mutationGroupBuilder.findTableDetailsBuilder( tableName ); - tableUpdateBuilder.addValueColumn( - mapping.getSelectionExpression(), - writePropertyValue ? "?" : columnValues[j], - mapping.getJdbcMapping(), - mapping.isLob() - ); + if ( mapping.isInsertable() ) { + final String tableName = entityPersister.physicalTableNameForMutation( mapping ); + final ColumnValuesTableMutationBuilder tableUpdateBuilder = mutationGroupBuilder.findTableDetailsBuilder( + tableName ); + tableUpdateBuilder.addValueColumn( + mapping.getSelectionExpression(), + writePropertyValue[j] ? "?" : columnValues[j], + mapping.getJdbcMapping(), + mapping.isLob() + ); + } + } ); + } + + protected void handleUpdatableValueGeneration( + AttributeMapping attributeMapping, + MutationGroupBuilder mutationGroupBuilder, + OnExecutionGenerator generator) { + if ( generator instanceof CompositeOnExecutionGenerator compositeOnExecutionGenerator ) { + handleUpdatableValueGeneration( attributeMapping, mutationGroupBuilder, compositeOnExecutionGenerator ); + } + else { + final Dialect dialect = factory.getJdbcServices().getDialect(); + final boolean writePropertyValue = generator.writePropertyValue(); + final String[] columnValues = writePropertyValue ? null : generator.getReferencedColumnValues( dialect ); + attributeMapping.forEachSelectable( (j, mapping) -> { + if ( mapping.isUpdateable() ) { + final String tableName = entityPersister.physicalTableNameForMutation( mapping ); + final ColumnValuesTableMutationBuilder tableUpdateBuilder = mutationGroupBuilder.findTableDetailsBuilder( + tableName ); + tableUpdateBuilder.addValueColumn( + mapping.getSelectionExpression(), + writePropertyValue ? "?" : columnValues[j], + mapping.getJdbcMapping(), + mapping.isLob() + ); + } + } ); + } + } + + protected void handleUpdatableValueGeneration( + AttributeMapping attributeMapping, + MutationGroupBuilder mutationGroupBuilder, + CompositeOnExecutionGenerator generator) { + final Dialect dialect = factory.getJdbcServices().getDialect(); + final boolean[] writePropertyValue = generator.writePropertyValues(); + final String[] columnValues = generator.getReferencedColumnValues( dialect ); + attributeMapping.forEachSelectable( (j, mapping) -> { + if ( mapping.isUpdateable() ) { + final String tableName = entityPersister.physicalTableNameForMutation( mapping ); + final ColumnValuesTableMutationBuilder tableUpdateBuilder = mutationGroupBuilder.findTableDetailsBuilder( + tableName ); + tableUpdateBuilder.addValueColumn( + mapping.getSelectionExpression(), + writePropertyValue[j] ? "?" : columnValues[j], + mapping.getJdbcMapping(), + mapping.isLob() + ); + } } ); } 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 ce084ce6acca..c38b0bd6a9e9 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 @@ -412,7 +412,10 @@ private void applyTableInsertDetails( attributeMapping.forEachInsertable( insertGroupBuilder ); } else if ( isValueGenerationInSql( generator, factory.getJdbcServices().getDialect() ) ) { - handleValueGeneration( attributeMapping, insertGroupBuilder, (OnExecutionGenerator) generator ); + handleInsertableValueGeneration( + attributeMapping, + insertGroupBuilder, + (OnExecutionGenerator) generator ); } } } 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 c7796072c8f3..05e713daba72 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 @@ -40,6 +40,7 @@ import org.hibernate.metamodel.mapping.EntityVersionMapping; import org.hibernate.metamodel.mapping.SelectableMapping; import org.hibernate.metamodel.mapping.SingularAttributeMapping; +import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.sql.model.MutationOperation; import org.hibernate.sql.model.MutationOperationGroup; @@ -664,7 +665,6 @@ private void processAttribute( InclusionChecker inclusionChecker, InclusionChecker lockingChecker, SharedSessionContractImplementor session) { - final Generator generator = attributeMapping.getGenerator(); final boolean generated = isValueGenerated( generator ); final boolean needsDynamicUpdate = @@ -1220,7 +1220,7 @@ private void applyAttributeUpdateDetails( SharedSessionContractImplementor session) { final Generator generator = attributeMapping.getGenerator(); if ( needsValueGeneration( entity, session, generator ) ) { - handleValueGeneration( attributeMapping, updateGroupBuilder, (OnExecutionGenerator) generator ); + handleUpdatableValueGeneration( attributeMapping, updateGroupBuilder, (OnExecutionGenerator) generator ); } else if ( versionMapping != null && versionMapping.getVersionAttribute() == attributeMapping) { @@ -1538,6 +1538,9 @@ else if ( this.dirty == DirtynessStatus.NOT_DIRTY ) { } public boolean isValueGeneratedInSqlNoWrite() { + if ( attribute instanceof EmbeddedAttributeMapping ) { + return false; + } return valueGeneratedInSqlNoWrite; } 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 04e7bc57364d..4d1611def72c 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 @@ -8,6 +8,7 @@ import org.hibernate.dialect.Dialect; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.generator.BeforeExecutionGenerator; +import org.hibernate.generator.CompositeOnExecutionGenerator; import org.hibernate.generator.EventType; import org.hibernate.generator.Generator; import org.hibernate.generator.OnExecutionGenerator; @@ -18,7 +19,9 @@ import java.util.ArrayList; import java.util.EnumSet; +import java.util.HashMap; import java.util.List; +import java.util.Map; import static java.lang.System.arraycopy; import static org.hibernate.generator.EventTypeSets.NONE; @@ -35,7 +38,7 @@ class CompositeGeneratorBuilder { private boolean hadBeforeExecutionGeneration; private boolean hadOnExecutionGeneration; - private final List generators = new ArrayList<>(); + private final Map generatorsByPropertyName = new HashMap<>(); public CompositeGeneratorBuilder(String entityName, Property mappingProperty, Dialect dialect) { this.entityName = entityName; @@ -43,8 +46,8 @@ public CompositeGeneratorBuilder(String entityName, Property mappingProperty, Di this.dialect = dialect; } - public void add(Generator generator) { - generators.add( generator ); + public void add(String propertyName, Generator generator) { + generatorsByPropertyName.put( propertyName, generator ); if ( generator != null && generator.generatesSometimes() ) { if ( generator.generatedOnExecution() ) { @@ -76,61 +79,74 @@ else if ( hadOnExecutionGeneration ) { private OnExecutionGenerator 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; boolean writable = false; boolean mutable = false; final String[] columnValues = new String[composite.getColumnSpan()]; + final boolean[] writesColumnValues = new boolean[composite.getColumnSpan()]; // start building the aggregate values int columnIndex = 0; final List properties = composite.getProperties(); + final Map generatorsByName = new HashMap<>(properties.size()); for ( int i = 0; i < properties.size(); i++ ) { final Property property = properties.get(i); - final OnExecutionGenerator generator = (OnExecutionGenerator) generators.get(i); - if ( generator == null ) { - throw new CompositeValueGenerationException( - "Property of on-execution generated embeddable is not generated: " - + mappingProperty.getName() + '.' + property.getName() - ); - } - 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 String name = property.getName(); + final OnExecutionGenerator generator = (OnExecutionGenerator) generatorsByPropertyName.get( name ); + generatorsByName.put( name, generator ); + final int span = property.getColumnSpan(); + final String[] referencedColumnValues; + if ( generator != null ) { + eventTypes.addAll( generator.getEventTypes() ); + if ( generator.referenceColumnsInSql( dialect ) ) { + // override base-line value + referenceColumns = true; + referencedColumnValues = generator.getReferencedColumnValues( dialect ); + if ( referencedColumnValues != null ) { + if ( referencedColumnValues.length != span ) { + throw new CompositeValueGenerationException( + "Mismatch between number of collected generated column values and number of columns for composite attribute: " + + mappingProperty.getName() + '.' + name + ); + } + arraycopy( referencedColumnValues, 0, columnValues, columnIndex, span ); + + } + } + if ( generator.writePropertyValue() ) { + writable = true; + for ( int j = columnIndex; j < columnIndex + span; j++ ) { + writesColumnValues[j] = true; } - arraycopy( referencedColumnValues, 0, columnValues, columnIndex, span ); - columnIndex += span; + } + if ( generator.allowMutation() ) { + mutable = true; } } - if ( generator.writePropertyValue() ) { + else { writable = true; - } - if ( generator.allowMutation() ) { + for ( int j = columnIndex; j < columnIndex + span; j++ ) { + writesColumnValues[j] = true; + } mutable = true; } + columnIndex += span; } // then use the aggregated values to build an OnExecutionGenerator - return new CompositeOnExecutionGenerator( eventTypes, referenceColumns, columnValues, writable, mutable ); + return new CompositeOnExecutionGeneratorImpl( eventTypes, generatorsByName, referenceColumns, columnValues, writable, writesColumnValues, mutable ); } private BeforeExecutionGenerator createCompositeBeforeExecutionGenerator() { final Component composite = (Component) mappingProperty.getValue(); final EnumSet eventTypes = EnumSet.noneOf(EventType.class); final List properties = composite.getProperties(); + final List generators = new ArrayList<>(properties.size()); for ( int i = 0; i < properties.size(); i++ ) { - final Generator generator = generators.get(i); + final Generator generator = generatorsByPropertyName.get( properties.get( i ).getName() ); + generators.add( generator ); if ( generator != null ) { eventTypes.addAll( generator.getEventTypes() ); } @@ -138,13 +154,15 @@ private BeforeExecutionGenerator createCompositeBeforeExecutionGenerator() { return new CompositeBeforeExecutionGenerator( entityName, generators, mappingProperty, properties, eventTypes ); } - private record CompositeOnExecutionGenerator( + public record CompositeOnExecutionGeneratorImpl( EnumSet eventTypes, + Map generatedPropertiesByName, boolean referenceColumnsInSql, String[] columnValues, boolean writePropertyValue, + boolean[] writePropertyValues, boolean allowMutation) - implements OnExecutionGenerator { + implements CompositeOnExecutionGenerator { @Override public boolean referenceColumnsInSql(Dialect dialect) { return referenceColumnsInSql; @@ -158,6 +176,12 @@ public String[] getReferencedColumnValues(Dialect dialect) { public EnumSet getEventTypes() { return eventTypes; } + + @Override + public OnExecutionGenerator getPropertyGenerator(String propertyName) { + return generatedPropertiesByName.get( propertyName ); + } + } private record CompositeBeforeExecutionGenerator( 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 f23eb84c3984..01331eab5119 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 @@ -529,20 +529,25 @@ private static Generator buildGenerator( final Property mappingProperty, final RuntimeModelCreationContext context) { final GeneratorCreator generatorCreator = mappingProperty.getValueGeneratorCreator(); - if ( generatorCreator != null ) { - final Generator generator = mappingProperty.createGenerator( context ); - if ( generator.generatesSometimes() ) { - return generator; - } - } if ( mappingProperty.getValue() instanceof Component component ) { final CompositeGeneratorBuilder builder = new CompositeGeneratorBuilder( entityName, mappingProperty, context.getDialect() ); for ( Property property : component.getProperties() ) { - builder.add( property.createGenerator( context ) ); + if ( property.getValue() instanceof Component ) { + builder.add( property.getName(), buildGenerator( entityName, property, context ) ); + } + else { + builder.add( property.getName(), property.createGenerator( context ) ); + } } return builder.build(); } + if ( generatorCreator != null ) { + final Generator generator = mappingProperty.createGenerator( context ); + if ( generator.generatesSometimes() ) { + return generator; + } + } return null; } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/CurrentTimeStampInsertEventTimeTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/CurrentTimeStampInsertEventTimeTest.java new file mode 100644 index 000000000000..6b76021da58f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/CurrentTimeStampInsertEventTimeTest.java @@ -0,0 +1,130 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.embeddable.generated; + +import jakarta.persistence.Column; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import org.hibernate.annotations.CurrentTimestamp; +import org.hibernate.annotations.SourceType; +import org.hibernate.generator.EventType; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.Test; + +import java.util.Date; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +@DomainModel( + annotatedClasses = { + CurrentTimeStampInsertEventTimeTest.TestEntity.class + } +) +@SessionFactory +@JiraKey("HHH-18756") +public class CurrentTimeStampInsertEventTimeTest { + + @Test + public void testCurrentTimeStamp(SessionFactoryScope scope) { + Long testEntityId = 1L; + String name = "a"; + String value = "b"; + Date now = new Date(); + + scope.inTransaction( + session -> { + TestEntity testEntity = new TestEntity( testEntityId, name, value ); + testEntity.anEmbeddable.setTimestamp( now ); + session.persist( testEntity ); + } + ); + + scope.inTransaction( + session -> { + TestEntity testEntity = session.find( TestEntity.class, testEntityId ); + assertThat( testEntity ).isNotNull(); + assertThat( testEntity.getName() ).isEqualTo( name ); + AnEmbeddable anEmbeddable = testEntity.getAnEmbeddable(); + assertThat( anEmbeddable ).isNotNull(); + assertThat( anEmbeddable.getaString() ).isEqualTo( value ); + assertThat( anEmbeddable.getTimestamp() ).isNull(); + anEmbeddable.setTimestamp( now ); + } + ); + + scope.inTransaction( session -> { + TestEntity testEntity = session.find( TestEntity.class, testEntityId ); + assertThat( testEntity ).isNotNull(); + assertThat( testEntity.getName() ).isEqualTo( name ); + AnEmbeddable anEmbeddable = testEntity.getAnEmbeddable(); + assertThat( anEmbeddable ).isNotNull(); + assertThat( anEmbeddable.getaString() ).isEqualTo( value ); + assertThat( anEmbeddable.getTimestamp() ).isNotNull(); + } ); + } + + @Entity(name = "TestEntity") + public static class TestEntity { + @Id + private Long id; + + private String name; + + @Embedded + private AnEmbeddable anEmbeddable; + + public TestEntity() { + } + + public TestEntity(Long id, String name, String aString) { + this.id = id; + this.name = name; + anEmbeddable = new AnEmbeddable( aString ); + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public AnEmbeddable getAnEmbeddable() { + return anEmbeddable; + } + } + + public static class AnEmbeddable { + @CurrentTimestamp(event = EventType.INSERT, source = SourceType.DB) + @Column(name = "timestamp_column") + private Date timestamp; + + private String aString; + + public AnEmbeddable() { + } + + public AnEmbeddable(String aString) { + this.aString = aString; + } + + public Date getTimestamp() { + return timestamp; + } + + public void setTimestamp(Date timestamp) { + this.timestamp = timestamp; + } + + public String getaString() { + return aString; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/CurrentTimeStampInsertUpdateEventTimeTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/CurrentTimeStampInsertUpdateEventTimeTest.java new file mode 100644 index 000000000000..a3fedfaa1793 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/CurrentTimeStampInsertUpdateEventTimeTest.java @@ -0,0 +1,134 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.embeddable.generated; + +import jakarta.persistence.Column; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import org.hibernate.annotations.CurrentTimestamp; +import org.hibernate.annotations.SourceType; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.Test; + +import java.util.Date; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +@DomainModel( + annotatedClasses = { + CurrentTimeStampInsertUpdateEventTimeTest.TestEntity.class + } +) +@SessionFactory +@JiraKey("HHH-18756") +public class CurrentTimeStampInsertUpdateEventTimeTest { + + @Test + public void testCurrentTimeStamp(SessionFactoryScope scope) { + Long testEntityId = 1L; + String name = "a"; + String value = "b"; + String updatedValue = "b1"; + scope.inTransaction( + session -> { + TestEntity testEntity = new TestEntity( testEntityId, name, value ); + session.persist( testEntity ); + } + ); + + Date now = new Date(); + + scope.inTransaction( + session -> { + TestEntity testEntity = session.find( TestEntity.class, testEntityId ); + assertThat( testEntity ).isNotNull(); + assertThat( testEntity.getName() ).isEqualTo( name ); + AnEmbeddable anEmbeddable = testEntity.getAnEmbeddable(); + assertThat( anEmbeddable ).isNotNull(); + assertThat( anEmbeddable.getaString() ).isEqualTo( value ); + assertThat( anEmbeddable.getTimestamp() ).isNull(); + anEmbeddable.setTimestamp( now ); + anEmbeddable.setaString( updatedValue ); + } + ); + + scope.inTransaction( session -> { + TestEntity testEntity = session.find( TestEntity.class, testEntityId ); + assertThat( testEntity ).isNotNull(); + assertThat( testEntity.getName() ).isEqualTo( name ); + AnEmbeddable anEmbeddable = testEntity.getAnEmbeddable(); + assertThat( anEmbeddable ).isNotNull(); + assertThat( anEmbeddable.getaString() ).isEqualTo( updatedValue ); + assertThat( anEmbeddable.getTimestamp() ).isNull(); + } ); + } + + @Entity(name = "TestEntity") + public static class TestEntity { + @Id + private Long id; + + private String name; + + @Embedded + private AnEmbeddable anEmbeddable; + + public TestEntity() { + } + + public TestEntity(Long id, String name, String aString) { + this.id = id; + this.name = name; + anEmbeddable = new AnEmbeddable( aString ); + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public AnEmbeddable getAnEmbeddable() { + return anEmbeddable; + } + } + + public static class AnEmbeddable { + @CurrentTimestamp(source = SourceType.DB) + @Column(name = "timestamp_column") + private Date timestamp; + + private String aString; + + public AnEmbeddable() { + } + + public AnEmbeddable(String aString) { + this.aString = aString; + } + + public Date getTimestamp() { + return timestamp; + } + + public void setTimestamp(Date timestamp) { + this.timestamp = timestamp; + } + + public String getaString() { + return aString; + } + + public void setaString(String aString) { + this.aString = aString; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/CurrentTimeStampUpdateEventTimeTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/CurrentTimeStampUpdateEventTimeTest.java new file mode 100644 index 000000000000..5573201a1438 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/CurrentTimeStampUpdateEventTimeTest.java @@ -0,0 +1,132 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.embeddable.generated; + +import jakarta.persistence.Column; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import org.hibernate.annotations.CurrentTimestamp; +import org.hibernate.annotations.SourceType; +import org.hibernate.generator.EventType; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.Test; + +import java.util.Date; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +@DomainModel( + annotatedClasses = { + CurrentTimeStampUpdateEventTimeTest.TestEntity.class + } +) +@SessionFactory +@JiraKey("HHH-18756") +public class CurrentTimeStampUpdateEventTimeTest { + + @Test + public void testCurrentTimeStamp(SessionFactoryScope scope) { + Long testEntityId = 1L; + String name = "a"; + String value = "b"; + Date now = new Date(); + + scope.inTransaction( + session -> { + TestEntity testEntity = new TestEntity( testEntityId, name, value ); + testEntity.getAnEmbeddable().setTimestamp( now ); + session.persist( testEntity ); + } + ); + + scope.inTransaction( + session -> { + TestEntity testEntity = session.find( TestEntity.class, testEntityId ); + assertThat( testEntity ).isNotNull(); + assertThat( testEntity.getName() ).isEqualTo( name ); + AnEmbeddable anEmbeddable = testEntity.getAnEmbeddable(); + assertThat( anEmbeddable ).isNotNull(); + assertThat( anEmbeddable.getaString() ).isEqualTo( value ); + // event time is UPDATE so it should be inserted + assertThat( anEmbeddable.getTimestamp() ).isNotNull(); + // but not updated + anEmbeddable.setTimestamp( null ); + } + ); + + scope.inTransaction( session -> { + TestEntity testEntity = session.find( TestEntity.class, testEntityId ); + assertThat( testEntity ).isNotNull(); + assertThat( testEntity.getName() ).isEqualTo( name ); + AnEmbeddable anEmbeddable = testEntity.getAnEmbeddable(); + assertThat( anEmbeddable ).isNotNull(); + assertThat( anEmbeddable.getaString() ).isEqualTo( value ); + assertThat( anEmbeddable.getTimestamp() ).isNotNull(); + } ); + } + + @Entity(name = "TestEntity") + public static class TestEntity { + @Id + private Long id; + + private String name; + + @Embedded + private AnEmbeddable anEmbeddable; + + public TestEntity() { + } + + public TestEntity(Long id, String name, String aString) { + this.id = id; + this.name = name; + anEmbeddable = new AnEmbeddable( aString ); + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public AnEmbeddable getAnEmbeddable() { + return anEmbeddable; + } + } + + public static class AnEmbeddable { + @CurrentTimestamp(event = EventType.UPDATE, source = SourceType.DB) + @Column(name = "timestamp_column") + private Date timestamp; + + private String aString; + + public AnEmbeddable() { + } + + public AnEmbeddable(String aString) { + this.aString = aString; + } + + public Date getTimestamp() { + return timestamp; + } + + public void setTimestamp(Date timestamp) { + this.timestamp = timestamp; + } + + public String getaString() { + return aString; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/EmbeddableInsertTimeGeneratedPropertiesTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/EmbeddableInsertTimeGeneratedPropertiesTest.java new file mode 100644 index 000000000000..df90f422b4b1 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/EmbeddableInsertTimeGeneratedPropertiesTest.java @@ -0,0 +1,114 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.embeddable.generated; + +import jakarta.persistence.Embeddable; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import org.hibernate.annotations.Generated; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +@DomainModel( + annotatedClasses = { + EmbeddableInsertTimeGeneratedPropertiesTest.TestEntity.class + } +) +@SessionFactory +@JiraKey("HHH-16957") +public class EmbeddableInsertTimeGeneratedPropertiesTest { + + @Test + public void testGeneratedProperties(SessionFactoryScope scope) { + Long id = 1L; + String nonGeneratedPropertyValue = "non generated"; + String generatedButWritablePropertyValue = "generated but writable"; + scope.inTransaction( + session -> { + TestEntity testEntity = new TestEntity( id, "generated", generatedButWritablePropertyValue, + nonGeneratedPropertyValue ); + session.persist( testEntity ); + } + ); + + scope.inTransaction( + session -> { + TestEntity testEntity = session.find( TestEntity.class, id ); + assertThat( testEntity.anEmbeddable.nonGeneratedProperty ) + .isEqualTo( nonGeneratedPropertyValue ); + assertThat( testEntity.anEmbeddable.generatedProperty ) + .isNull(); + assertThat( testEntity.anEmbeddable.generatedButWritableProperty ) + .isEqualTo( generatedButWritablePropertyValue ); + } + ); + } + + @Entity(name = "TestEntity") + public static class TestEntity { + @Id + private Long id; + + @Embedded + private AnEmbeddable anEmbeddable; + + + public TestEntity() { + } + + public TestEntity(Long id, String nonGeneratedProperty) { + this.id = id; + this.anEmbeddable = new AnEmbeddable( nonGeneratedProperty ); + } + + public TestEntity(Long id, String generatedButWritableProperty, String nonGeneratedProperty) { + this.id = id; + this.anEmbeddable = new AnEmbeddable( generatedButWritableProperty, nonGeneratedProperty ); + } + + public TestEntity(Long id, String generatedProperty, String generatedButWritableProperty, String nonGeneratedProperty) { + this.id = id; + this.anEmbeddable = new AnEmbeddable( generatedProperty, generatedButWritableProperty, + nonGeneratedProperty ); + } + } + + @Embeddable + public static class AnEmbeddable { + @Generated + private String generatedProperty; + + @Generated(writable = true) + private String generatedButWritableProperty; + + private String nonGeneratedProperty; + + + public AnEmbeddable() { + } + + public AnEmbeddable(String nonGeneratedProperty) { + this.nonGeneratedProperty = nonGeneratedProperty; + } + + public AnEmbeddable(String generatedButWritableProperty, String nonGeneratedProperty) { + this.generatedButWritableProperty = generatedButWritableProperty; + this.nonGeneratedProperty = nonGeneratedProperty; + } + + public AnEmbeddable(String generatedProperty, String generatedButWritableProperty, String nonGeneratedProperty) { + this.generatedProperty = generatedProperty; + this.generatedButWritableProperty = generatedButWritableProperty; + this.nonGeneratedProperty = nonGeneratedProperty; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/EmbeddableTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/EmbeddableTest.java new file mode 100644 index 000000000000..17108182e59c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/EmbeddableTest.java @@ -0,0 +1,91 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.embeddable.generated; + +import jakarta.persistence.Embeddable; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import org.hibernate.annotations.Generated; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +@DomainModel( + annotatedClasses = { + EmbeddableTest.TestEntity.class + } +) +@SessionFactory +@JiraKey("HHH-16957") +public class EmbeddableTest { + + @Test + public void testEmbeddable(SessionFactoryScope scope) { + Long id = 1L; + String nonGeneratedPropertyValue = "non generated"; + scope.inTransaction( + session -> { + TestEntity testEntity = new TestEntity( id, nonGeneratedPropertyValue ); + session.persist( testEntity ); + } + ); + + scope.inTransaction( + session -> { + TestEntity testEntity = session.find( TestEntity.class, id ); + assertThat( testEntity.anEmbeddable.nonGeneratedProperty ).isEqualTo( nonGeneratedPropertyValue ); + } + ); + } + + @Entity(name = "TestEntity") + public static class TestEntity { + @Id + private Long id; + + @Embedded + private AnEmbeddable anEmbeddable; + + + public TestEntity() { + } + + public TestEntity(Long id, String generatedProperty, String nonGeneratedProperty) { + this.id = id; + this.anEmbeddable = new AnEmbeddable( generatedProperty, nonGeneratedProperty ); + } + + public TestEntity(Long id, String nonGeneratedProperty) { + this.id = id; + this.anEmbeddable = new AnEmbeddable( nonGeneratedProperty ); + } + } + + @Embeddable + public static class AnEmbeddable { + @Generated + private String generatedProperty; + + private String nonGeneratedProperty; + + + public AnEmbeddable() { + } + + public AnEmbeddable(String generatedProperty, String nonGeneratedProperty) { + this.generatedProperty = generatedProperty; + this.nonGeneratedProperty = nonGeneratedProperty; + } + + public AnEmbeddable(String nonGeneratedProperty) { + this.nonGeneratedProperty = nonGeneratedProperty; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/EmbeddableUpdateTimeGeneratedPropertiesTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/EmbeddableUpdateTimeGeneratedPropertiesTest.java new file mode 100644 index 000000000000..e33743897d59 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/EmbeddableUpdateTimeGeneratedPropertiesTest.java @@ -0,0 +1,167 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.embeddable.generated; + +import jakarta.persistence.Embeddable; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import org.hibernate.annotations.Generated; +import org.hibernate.generator.EventType; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +@DomainModel( + annotatedClasses = { + EmbeddableUpdateTimeGeneratedPropertiesTest.TestEntity.class + } +) +@SessionFactory +@JiraKey("HHH-16957") +public class EmbeddableUpdateTimeGeneratedPropertiesTest { + + @AfterEach + public void tearDown(SessionFactoryScope sessionFactoryScope) { + sessionFactoryScope.getSessionFactory().getSchemaManager().truncateMappedObjects(); + } + + @Test + public void testGeneratedProperties(SessionFactoryScope scope) { + Long id = 1L; + String nonGeneratedPropertyValue = "non generated"; + String generatedButWritablePropertyValue = "generated but writable"; + String generatedPropertyValue = "generated"; + + scope.inTransaction( + session -> { + TestEntity testEntity = new TestEntity( id, generatedPropertyValue, + generatedButWritablePropertyValue, + nonGeneratedPropertyValue ); + session.persist( testEntity ); + } + ); + + scope.inTransaction( + session -> { + TestEntity testEntity = session.find( TestEntity.class, id ); + assertThat( testEntity.anEmbeddable.nonGeneratedProperty ) + .isEqualTo( nonGeneratedPropertyValue ); + assertThat( testEntity.anEmbeddable.generatedProperty ) + .isEqualTo( generatedPropertyValue ); + assertThat( testEntity.anEmbeddable.generatedButWritableProperty ) + .isEqualTo( generatedButWritablePropertyValue ); + } + ); + } + + @Test + public void testGeneratedProperties2(SessionFactoryScope scope) { + Long id = 1L; + String nonGeneratedPropertyValue = "non generated"; + scope.inTransaction( + session -> { + TestEntity testEntity = new TestEntity( id, nonGeneratedPropertyValue ); + session.persist( testEntity ); + } + ); + + String generatedButWritablePropertyValue = "generated but writable"; + String generatedPropertyValue = "generated"; + + scope.inTransaction( + session -> { + TestEntity testEntity = session.find( TestEntity.class, id ); + assertThat( testEntity.anEmbeddable.nonGeneratedProperty ) + .isEqualTo( nonGeneratedPropertyValue ); + assertThat( testEntity.anEmbeddable.generatedProperty ) + .isEqualTo( null ); + assertThat( testEntity.anEmbeddable.generatedButWritableProperty ) + .isEqualTo( null ); + testEntity.anEmbeddable.generatedButWritableProperty = generatedButWritablePropertyValue; + testEntity.anEmbeddable.generatedProperty = generatedPropertyValue; + } + ); + + scope.inTransaction( + session -> { + TestEntity testEntity = session.find( TestEntity.class, id ); + assertThat( testEntity.anEmbeddable.nonGeneratedProperty ) + .isEqualTo( nonGeneratedPropertyValue ); + assertThat( testEntity.anEmbeddable.generatedProperty ) + .isEqualTo( null ); + assertThat( testEntity.anEmbeddable.generatedButWritableProperty ) + .isEqualTo( generatedButWritablePropertyValue ); + testEntity.name = "a"; + } + ); + } + + @Entity(name = "TestEntity") + public static class TestEntity { + @Id + private Long id; + + private String name; + + @Embedded + private AnEmbeddable anEmbeddable; + + + public TestEntity() { + } + + public TestEntity(Long id, String nonGeneratedProperty) { + this.id = id; + this.anEmbeddable = new AnEmbeddable( nonGeneratedProperty ); + } + + public TestEntity(Long id, String generatedButWritableProperty, String nonGeneratedProperty) { + this.id = id; + this.anEmbeddable = new AnEmbeddable( generatedButWritableProperty, nonGeneratedProperty ); + } + + public TestEntity(Long id, String generatedProperty, String generatedButWritableProperty, String nonGeneratedProperty) { + this.id = id; + this.anEmbeddable = new AnEmbeddable( generatedProperty, generatedButWritableProperty, + nonGeneratedProperty ); + } + } + + @Embeddable + public static class AnEmbeddable { + @Generated(event = EventType.UPDATE) + private String generatedProperty; + + @Generated(event = EventType.UPDATE, writable = true) + private String generatedButWritableProperty; + + private String nonGeneratedProperty; + + + public AnEmbeddable() { + } + + public AnEmbeddable(String nonGeneratedProperty) { + this.nonGeneratedProperty = nonGeneratedProperty; + } + + public AnEmbeddable(String generatedButWritableProperty, String nonGeneratedProperty) { + this.generatedButWritableProperty = generatedButWritableProperty; + this.nonGeneratedProperty = nonGeneratedProperty; + } + + public AnEmbeddable(String generatedProperty, String generatedButWritableProperty, String nonGeneratedProperty) { + this.generatedProperty = generatedProperty; + this.generatedButWritableProperty = generatedButWritableProperty; + this.nonGeneratedProperty = nonGeneratedProperty; + } + } +} 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..e81122dd8bd4 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/InsertGeneratedTest.java @@ -0,0 +1,363 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.embeddable.generated; + +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 org.hibernate.annotations.Generated; +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 java.sql.PreparedStatement; + +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 , " + + INSERT_GENERATED_PROP_WRITABLE_0_COLUMN + " integer, " + + NON_GENERATED_PROP_1_COLUMN + " integer, " + + INSERT_GENERATED_PROP_1_COLUMN + " integer , " + + 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_0_COLUMN + " = " + INSERT_GENERATED_PROP_0 + " , " + + INSERT_GENERATED_PROP_1_COLUMN + " = " + INSERT_GENERATED_PROP_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( 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 ).contains( INSERT_GENERATED_PROP_0_COLUMN ); + assertThat( updateQuery ).contains( 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 + private Integer insertGeneratedProp0; + + @Column(name = INSERT_GENERATED_PROP_WRITABLE_0_COLUMN) + @Generated(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 + @Column(name = INSERT_GENERATED_PROP_1_COLUMN) + private Integer insertGeneratedProperty1; + + @Generated(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/NestedEmbeddableCurrentTimeStampInsertEventTimeTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/NestedEmbeddableCurrentTimeStampInsertEventTimeTest.java new file mode 100644 index 000000000000..61d3f93b940b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/NestedEmbeddableCurrentTimeStampInsertEventTimeTest.java @@ -0,0 +1,195 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.embeddable.generated; + +import jakarta.persistence.Column; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import org.hibernate.annotations.CurrentTimestamp; +import org.hibernate.annotations.SourceType; +import org.hibernate.generator.EventType; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.Test; + +import java.util.Date; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +@DomainModel( + annotatedClasses = { + NestedEmbeddableCurrentTimeStampInsertEventTimeTest.TestEntity.class + } +) +@SessionFactory +@JiraKey("HHH-18756") +public class NestedEmbeddableCurrentTimeStampInsertEventTimeTest { + + @Test + public void testCurrentTimeStamp(SessionFactoryScope scope) { + Long testEntityId = 1L; + String name = "a"; + String value = "b"; + String anotherValue = "c"; + String updatedValue = "b1"; + scope.inTransaction( + session -> { + TestEntity testEntity = new TestEntity( testEntityId, name, value, anotherValue ); + testEntity.getAnEmbeddable().setTimestamp( new Date() ); + testEntity.getAnotherEmbeddable().setAnotherTimestamp( new Date() ); + session.persist( testEntity ); + } + ); + + Date now = new Date(); + + scope.inTransaction( + session -> { + TestEntity testEntity = session.find( TestEntity.class, testEntityId ); + assertThat( testEntity ).isNotNull(); + assertThat( testEntity.getName() ).isEqualTo( name ); + + AnEmbeddable anEmbeddable = testEntity.getAnEmbeddable(); + assertThat( anEmbeddable ).isNotNull(); + assertThat( anEmbeddable.getaString() ).isEqualTo( value ); + assertThat( anEmbeddable.getTimestamp() ).isNull(); + + AnotherEmbeddable anotherEmbeddable = testEntity.getAnotherEmbeddable(); + assertThat( anotherEmbeddable ).isNotNull(); + assertThat( anotherEmbeddable.getAnotherString() ).isEqualTo( anotherValue ); + assertThat( anotherEmbeddable.getAnotherTimestamp() ).isNull(); + + anEmbeddable.setTimestamp( now ); + anEmbeddable.setaString( updatedValue ); + anotherEmbeddable.setAnotherTimestamp( new Date( 1971, 1, 1 ) ); + } + ); + + scope.inTransaction( session -> { + TestEntity testEntity = session.find( TestEntity.class, testEntityId ); + assertThat( testEntity ).isNotNull(); + assertThat( testEntity.getName() ).isEqualTo( name ); + + AnEmbeddable anEmbeddable = testEntity.getAnEmbeddable(); + assertThat( anEmbeddable ).isNotNull(); + assertThat( anEmbeddable.getaString() ).isEqualTo( updatedValue ); + assertThat( anEmbeddable.getTimestamp() ).isNotNull(); + + AnotherEmbeddable anotherEmbeddable = testEntity.getAnotherEmbeddable(); + assertThat( anotherEmbeddable ).isNotNull(); + assertThat( anotherEmbeddable.getAnotherString() ).isEqualTo( anotherValue ); + assertThat( anotherEmbeddable.getAnotherTimestamp() ).isNotNull(); + } ); + } + + @Entity(name = "TestEntity") + public static class TestEntity { + @Id + private Long id; + + private String name; + + @Embedded + private AnEmbeddable anEmbeddable; + + public TestEntity() { + } + + public TestEntity(Long id, String name, String aString, String anotherString) { + this.id = id; + this.name = name; + anEmbeddable = new AnEmbeddable( aString, anotherString ); + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public AnEmbeddable getAnEmbeddable() { + return anEmbeddable; + } + + public AnotherEmbeddable getAnotherEmbeddable() { + return anEmbeddable.getAnotherEmbeddable(); + } + } + + public static class AnEmbeddable { + @CurrentTimestamp(event = EventType.INSERT, source = SourceType.DB) + @Column(name = "timestamp_column") + private Date timestamp; + + private String aString; + + @Embedded + private AnotherEmbeddable anotherEmbeddable; + + public AnEmbeddable() { + } + + public AnEmbeddable(String aString, String anotherString) { + this.aString = aString; + anotherEmbeddable = new AnotherEmbeddable( anotherString ); + } + + public Date getTimestamp() { + return timestamp; + } + + public void setTimestamp(Date timestamp) { + this.timestamp = timestamp; + } + + public String getaString() { + return aString; + } + + public void setaString(String aString) { + this.aString = aString; + } + + public AnotherEmbeddable getAnotherEmbeddable() { + return anotherEmbeddable; + } + } + + public static class AnotherEmbeddable { + @CurrentTimestamp(event = EventType.INSERT, source = SourceType.DB) + @Column(name = "another_timestamp_column") + private Date anotherTimestamp; + + private String anotherString; + + public AnotherEmbeddable() { + } + + public AnotherEmbeddable(String aString) { + this.anotherString = aString; + } + + public Date getAnotherTimestamp() { + return anotherTimestamp; + } + + public void setAnotherTimestamp(Date anotherTimestamp) { + this.anotherTimestamp = anotherTimestamp; + } + + public String getAnotherString() { + return anotherString; + } + + public void setAnotherString(String anotherString) { + this.anotherString = anotherString; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/NestedEmbeddableCurrentTimeStampInsertUpdateEventTimeTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/NestedEmbeddableCurrentTimeStampInsertUpdateEventTimeTest.java new file mode 100644 index 000000000000..5b594f694c44 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/NestedEmbeddableCurrentTimeStampInsertUpdateEventTimeTest.java @@ -0,0 +1,193 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.embeddable.generated; + +import jakarta.persistence.Column; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import org.hibernate.annotations.CurrentTimestamp; +import org.hibernate.annotations.SourceType; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.Test; + +import java.util.Date; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +@DomainModel( + annotatedClasses = { + NestedEmbeddableCurrentTimeStampInsertUpdateEventTimeTest.TestEntity.class + } +) +@SessionFactory +@JiraKey("HHH-18756") +public class NestedEmbeddableCurrentTimeStampInsertUpdateEventTimeTest { + + @Test + public void testCurrentTimeStamp(SessionFactoryScope scope) { + Long testEntityId = 1L; + String name = "a"; + String value = "b"; + String anotherValue = "c"; + String updatedValue = "b1"; + scope.inTransaction( + session -> { + TestEntity testEntity = new TestEntity( testEntityId, name, value, anotherValue ); + session.persist( testEntity ); + } + ); + + Date now = new Date(); + + scope.inTransaction( + session -> { + TestEntity testEntity = session.find( TestEntity.class, testEntityId ); + assertThat( testEntity ).isNotNull(); + assertThat( testEntity.getName() ).isEqualTo( name ); + + AnEmbeddable anEmbeddable = testEntity.getAnEmbeddable(); + assertThat( anEmbeddable ).isNotNull(); + assertThat( anEmbeddable.getaString() ).isEqualTo( value ); + assertThat( anEmbeddable.getTimestamp() ).isNull(); + + AnotherEmbeddable anotherEmbeddable = testEntity.getAnotherEmbeddable(); + assertThat( anotherEmbeddable ).isNotNull(); + assertThat( anotherEmbeddable.getAnotherString() ).isEqualTo( anotherValue ); + assertThat( anotherEmbeddable.getAnotherTimestamp() ).isNull(); + + anEmbeddable.setTimestamp( now ); + anEmbeddable.setaString( updatedValue ); + anotherEmbeddable.setAnotherTimestamp( now ); + } + ); + + scope.inTransaction( session -> { + TestEntity testEntity = session.find( TestEntity.class, testEntityId ); + assertThat( testEntity ).isNotNull(); + assertThat( testEntity.getName() ).isEqualTo( name ); + + AnEmbeddable anEmbeddable = testEntity.getAnEmbeddable(); + assertThat( anEmbeddable ).isNotNull(); + assertThat( anEmbeddable.getaString() ).isEqualTo( updatedValue ); + assertThat( anEmbeddable.getTimestamp() ).isNull(); + + AnotherEmbeddable anotherEmbeddable = testEntity.getAnotherEmbeddable(); + assertThat( anotherEmbeddable ).isNotNull(); + assertThat( anotherEmbeddable.getAnotherString() ).isEqualTo( anotherValue ); + assertThat( anotherEmbeddable.getAnotherTimestamp() ).isNull(); + } ); + } + + @Entity(name = "TestEntity") + public static class TestEntity { + @Id + private Long id; + + private String name; + + @Embedded + private AnEmbeddable anEmbeddable; + + public TestEntity() { + } + + public TestEntity(Long id, String name, String aString, String anotherString) { + this.id = id; + this.name = name; + anEmbeddable = new AnEmbeddable( aString, anotherString ); + + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public AnEmbeddable getAnEmbeddable() { + return anEmbeddable; + } + + public AnotherEmbeddable getAnotherEmbeddable() { + return anEmbeddable.getAnotherEmbeddable(); + } + } + + public static class AnEmbeddable { + @CurrentTimestamp(source = SourceType.DB) + @Column(name = "timestamp_column") + private Date timestamp; + + private String aString; + + @Embedded + private AnotherEmbeddable anotherEmbeddable; + + public AnEmbeddable() { + } + + public AnEmbeddable(String aString, String anotherString) { + this.aString = aString; + anotherEmbeddable = new AnotherEmbeddable( anotherString ); + } + + public Date getTimestamp() { + return timestamp; + } + + public void setTimestamp(Date timestamp) { + this.timestamp = timestamp; + } + + public String getaString() { + return aString; + } + + public void setaString(String aString) { + this.aString = aString; + } + + public AnotherEmbeddable getAnotherEmbeddable() { + return anotherEmbeddable; + } + } + + public static class AnotherEmbeddable { + @CurrentTimestamp(source = SourceType.DB) + @Column(name = "another_timestamp_column") + private Date anotherTimestamp; + + private String anotherString; + + public AnotherEmbeddable() { + } + + public AnotherEmbeddable(String aString) { + this.anotherString = aString; + } + + public Date getAnotherTimestamp() { + return anotherTimestamp; + } + + public void setAnotherTimestamp(Date anotherTimestamp) { + this.anotherTimestamp = anotherTimestamp; + } + + public String getAnotherString() { + return anotherString; + } + + public void setAnotherString(String anotherString) { + this.anotherString = anotherString; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/NestedEmbeddableCurrentTimeStampUpdateEventTimeTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/NestedEmbeddableCurrentTimeStampUpdateEventTimeTest.java new file mode 100644 index 000000000000..241cf51c8277 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/NestedEmbeddableCurrentTimeStampUpdateEventTimeTest.java @@ -0,0 +1,192 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.embeddable.generated; + +import jakarta.persistence.Column; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import org.hibernate.annotations.CurrentTimestamp; +import org.hibernate.annotations.SourceType; +import org.hibernate.generator.EventType; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.Test; + +import java.util.Date; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +@DomainModel( + annotatedClasses = { + NestedEmbeddableCurrentTimeStampUpdateEventTimeTest.TestEntity.class + } +) +@SessionFactory +@JiraKey("HHH-18756") +public class NestedEmbeddableCurrentTimeStampUpdateEventTimeTest { + + @Test + public void testCurrentTimeStamp(SessionFactoryScope scope) { + Long testEntityId = 1L; + String name = "a"; + String value = "b"; + String anotherValue = "c"; + String updatedValue = "b1"; + scope.inTransaction( + session -> { + TestEntity testEntity = new TestEntity( testEntityId, name, value, anotherValue ); + session.persist( testEntity ); + } + ); + + Date now = new Date(); + + scope.inTransaction( + session -> { + TestEntity testEntity = session.find( TestEntity.class, testEntityId ); + assertThat( testEntity ).isNotNull(); + assertThat( testEntity.getName() ).isEqualTo( name ); + + AnEmbeddable anEmbeddable = testEntity.getAnEmbeddable(); + assertThat( anEmbeddable ).isNotNull(); + assertThat( anEmbeddable.getaString() ).isEqualTo( value ); + assertThat( anEmbeddable.getTimestamp() ).isNull(); + + AnotherEmbeddable anotherEmbeddable = testEntity.getAnotherEmbeddable(); + assertThat( anotherEmbeddable ).isNotNull(); + assertThat( anotherEmbeddable.getAnotherString() ).isEqualTo( anotherValue ); + assertThat( anotherEmbeddable.getAnotherTimestamp() ).isNull(); + + anEmbeddable.setaString( updatedValue ); + anotherEmbeddable.setAnotherTimestamp( now ); + } + ); + + scope.inTransaction( session -> { + TestEntity testEntity = session.find( TestEntity.class, testEntityId ); + assertThat( testEntity ).isNotNull(); + assertThat( testEntity.getName() ).isEqualTo( name ); + + AnEmbeddable anEmbeddable = testEntity.getAnEmbeddable(); + assertThat( anEmbeddable ).isNotNull(); + assertThat( anEmbeddable.getaString() ).isEqualTo( updatedValue ); + assertThat( anEmbeddable.getTimestamp() ).isNull(); + + AnotherEmbeddable anotherEmbeddable = testEntity.getAnotherEmbeddable(); + assertThat( anotherEmbeddable ).isNotNull(); + assertThat( anotherEmbeddable.getAnotherString() ).isEqualTo( anotherValue ); + assertThat( anotherEmbeddable.getAnotherTimestamp() ).isNull(); + } ); + } + + @Entity(name = "TestEntity") + public static class TestEntity { + @Id + private Long id; + + private String name; + + @Embedded + private AnEmbeddable anEmbeddable; + + public TestEntity() { + } + + public TestEntity(Long id, String name, String aString, String anotherString) { + this.id = id; + this.name = name; + anEmbeddable = new AnEmbeddable( aString, anotherString ); + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public AnEmbeddable getAnEmbeddable() { + return anEmbeddable; + } + + public AnotherEmbeddable getAnotherEmbeddable() { + return anEmbeddable.getAnotherEmbeddable(); + } + } + + public static class AnEmbeddable { + @CurrentTimestamp(event = EventType.UPDATE, source = SourceType.DB) + @Column(name = "timestamp_column") + private Date timestamp; + + private String aString; + + @Embedded + private AnotherEmbeddable anotherEmbeddable; + + public AnEmbeddable() { + } + + public AnEmbeddable(String aString, String anotherString) { + this.aString = aString; + anotherEmbeddable = new AnotherEmbeddable( anotherString ); + } + + public Date getTimestamp() { + return timestamp; + } + + public void setTimestamp(Date timestamp) { + this.timestamp = timestamp; + } + + public String getaString() { + return aString; + } + + public void setaString(String aString) { + this.aString = aString; + } + + public AnotherEmbeddable getAnotherEmbeddable() { + return anotherEmbeddable; + } + } + + public static class AnotherEmbeddable { + @CurrentTimestamp(event = EventType.UPDATE, source = SourceType.DB) + @Column(name = "another_timestamp_column") + private Date anotherTimestamp; + + private String anotherString; + + public AnotherEmbeddable() { + } + + public AnotherEmbeddable(String aString) { + this.anotherString = aString; + } + + public Date getAnotherTimestamp() { + return anotherTimestamp; + } + + public void setAnotherTimestamp(Date anotherTimestamp) { + this.anotherTimestamp = anotherTimestamp; + } + + public String getAnotherString() { + return anotherString; + } + + public void setAnotherString(String anotherString) { + this.anotherString = anotherString; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/NestedEmbeddableInsertTimeGeneratedPropertiesTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/NestedEmbeddableInsertTimeGeneratedPropertiesTest.java new file mode 100644 index 000000000000..28e12f07ef89 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/NestedEmbeddableInsertTimeGeneratedPropertiesTest.java @@ -0,0 +1,135 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.embeddable.generated; + +import jakarta.persistence.Embeddable; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import org.hibernate.annotations.Generated; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +@DomainModel( + annotatedClasses = { + NestedEmbeddableInsertTimeGeneratedPropertiesTest.TestEntity.class + } +) +@SessionFactory +@JiraKey("HHH-16957") +public class NestedEmbeddableInsertTimeGeneratedPropertiesTest { + + @Test + public void testGeneratedProperties(SessionFactoryScope scope) { + Long id = 1L; + String nonGeneratedPropertyValue = "non generated"; + String generatedButWritablePropertyValue = "generated but writable"; + scope.inTransaction( + session -> { + TestEntity testEntity = new TestEntity( id, "generated", generatedButWritablePropertyValue, + nonGeneratedPropertyValue ); + session.persist( testEntity ); + } + ); + + scope.inTransaction( + session -> { + TestEntity testEntity = session.find( TestEntity.class, id ); + assertThat( testEntity.anEmbeddable.secondEmbeddable.secondNonGeneratedProperty ) + .isEqualTo( nonGeneratedPropertyValue ); + assertThat( testEntity.anEmbeddable.secondEmbeddable.secondGeneratedProperty ) + .isNull(); + assertThat( testEntity.anEmbeddable.secondEmbeddable.secondGeneratedButWritableProperty ) + .isEqualTo( generatedButWritablePropertyValue ); + } + ); + } + + @Entity(name = "TestEntity") + public static class TestEntity { + @Id + private Long id; + + @Embedded + private AnEmbeddable anEmbeddable; + + + public TestEntity() { + } + + public TestEntity(Long id, String nonGeneratedProperty) { + this.id = id; + this.anEmbeddable = new AnEmbeddable( nonGeneratedProperty ); + } + + public TestEntity(Long id, String generatedButWritableProperty, String nonGeneratedProperty) { + this.id = id; + this.anEmbeddable = new AnEmbeddable( generatedButWritableProperty, nonGeneratedProperty ); + } + + public TestEntity(Long id, String generatedProperty, String generatedButWritableProperty, String nonGeneratedProperty) { + this.id = id; + this.anEmbeddable = new AnEmbeddable( generatedProperty, generatedButWritableProperty, + nonGeneratedProperty ); + } + } + + @Embeddable + public static class AnEmbeddable { + ASecondEmbeddable secondEmbeddable; + + + public AnEmbeddable() { + } + + public AnEmbeddable(String nonGeneratedProperty) { + + this.secondEmbeddable = new ASecondEmbeddable( nonGeneratedProperty ); + } + + public AnEmbeddable(String generatedButWritableProperty, String nonGeneratedProperty) { + this.secondEmbeddable = new ASecondEmbeddable( generatedButWritableProperty, nonGeneratedProperty ); + } + + public AnEmbeddable(String generatedProperty, String generatedButWritableProperty, String nonGeneratedProperty) { + this.secondEmbeddable = new ASecondEmbeddable( generatedProperty, generatedButWritableProperty, + nonGeneratedProperty ); + } + } + + @Embeddable + public static class ASecondEmbeddable { + @Generated + private String secondGeneratedProperty; + + @Generated(writable = true) + private String secondGeneratedButWritableProperty; + + private String secondNonGeneratedProperty; + + public ASecondEmbeddable() { + } + + public ASecondEmbeddable(String secondNonGeneratedProperty) { + this.secondNonGeneratedProperty = secondNonGeneratedProperty; + } + + public ASecondEmbeddable(String secondGeneratedProperty, String secondGeneratedButWritableProperty) { + this.secondGeneratedButWritableProperty = secondGeneratedProperty; + this.secondNonGeneratedProperty = secondGeneratedButWritableProperty; + } + + public ASecondEmbeddable(String secondGeneratedProperty, String secondGeneratedButWritableProperty, String secondNonGeneratedProperty) { + this.secondGeneratedProperty = secondGeneratedProperty; + this.secondGeneratedButWritableProperty = secondGeneratedButWritableProperty; + this.secondNonGeneratedProperty = secondNonGeneratedProperty; + } + } +} 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..a8761f8645b4 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/embeddable/generated/NestedEmbeddableTest.java @@ -0,0 +1,92 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.embeddable.generated; + +import java.util.HashSet; +import java.util.Set; + +import org.hibernate.annotations.Generated; + +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.persist( 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 + 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 + private String property2; + + public AnotherEmbeddable() { + } + + public AnotherEmbeddable(String property2) { + this.property2 = property2; + } + } +}