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..302a57ac24e1 --- /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 { + Generator getGenerator(String propertyName); +} 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..2a0143a45385 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/generator/internal/OnExecutionCompositeGenerator.java @@ -0,0 +1,67 @@ +/* + * 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.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 Map generators; + final EnumSet eventTypes; + final boolean writePropertyValue; + final boolean referenceColumnsInSql; + final String[] referencedColumnValues; + + public OnExecutionCompositeGenerator( + Map 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(String propertyName) { + return generators.get( propertyName ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderSelectBuilder.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderSelectBuilder.java index 7a078bdb840f..341f7ff3bfc4 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderSelectBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderSelectBuilder.java @@ -48,7 +48,6 @@ import org.hibernate.spi.EntityIdentifierNavigablePath; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.ast.SqlAstJoinType; -import org.hibernate.sql.ast.internal.TableGroupJoinHelper; import org.hibernate.sql.ast.spi.AliasCollector; import org.hibernate.sql.ast.spi.FromClauseAccess; import org.hibernate.sql.ast.spi.SimpleFromClauseAccessImpl; diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractEmbeddableMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractEmbeddableMapping.java index 355c63a337cf..6687a69908e0 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractEmbeddableMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractEmbeddableMapping.java @@ -23,6 +23,7 @@ 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.internal.util.IndexedConsumer; import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.mapping.Any; @@ -268,6 +269,7 @@ protected boolean finishInitialization( ConcreteTableResolver concreteTableResolver, Consumer attributeConsumer, SuccessfulCompletionCallback completionCallback, + Generator generator, MappingModelCreationProcess creationProcess) { final TypeConfiguration typeConfiguration = creationProcess.getCreationContext().getTypeConfiguration(); final JdbcServices jdbcServices = creationProcess.getCreationContext().getJdbcServices(); @@ -278,7 +280,6 @@ protected boolean finishInitialization( int attributeIndex = 0; int columnPosition = 0; - for ( Property bootPropertyDescriptor : bootDescriptor.getProperties() ) { final Type subtype = subtypes[ attributeIndex ]; @@ -373,6 +374,7 @@ protected boolean finishInitialization( value.isColumnUpdateable( 0 ), propertyAccess, compositeType.getCascadeStyle( attributeIndex ), + generator, creationProcess ); @@ -440,6 +442,7 @@ else if ( subtype instanceof CompositeType ) { subRootTableKeyColumnNames, propertyAccess, compositeType.getCascadeStyle( attributeIndex ), + generator, creationProcess ); @@ -473,6 +476,7 @@ else if ( subtype instanceof EntityType ) { (EntityType) subtype, representationStrategy.resolvePropertyAccess( bootPropertyDescriptor ), compositeType.getCascadeStyle( attributeIndex ), + generator, creationProcess ); columnPosition += bootPropertyDescriptor.getColumnSpan(); 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 8fb1cf02dfb1..44646c2a1f7e 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 @@ -23,6 +23,7 @@ public abstract class AbstractSingularAttributeMapping implements SingularAttributeMapping { private final PropertyAccess propertyAccess; + private final Generator generator; public AbstractSingularAttributeMapping( String name, @@ -31,9 +32,11 @@ public AbstractSingularAttributeMapping( AttributeMetadata attributeMetadata, FetchOptions mappedFetchOptions, ManagedMappingType declaringType, + Generator generator, PropertyAccess propertyAccess) { super( name, attributeMetadata, mappedFetchOptions, stateArrayPosition, fetchableIndex, declaringType ); this.propertyAccess = propertyAccess; + this.generator = generator; } public AbstractSingularAttributeMapping( @@ -44,9 +47,11 @@ public AbstractSingularAttributeMapping( FetchTiming fetchTiming, FetchStyle fetchStyle, ManagedMappingType declaringType, + Generator generator, PropertyAccess propertyAccess) { super( name, attributeMetadata, fetchTiming, fetchStyle, stateArrayPosition, fetchableIndex, declaringType ); this.propertyAccess = propertyAccess; + this.generator = generator; } /** @@ -55,6 +60,7 @@ public AbstractSingularAttributeMapping( protected AbstractSingularAttributeMapping( AbstractSingularAttributeMapping original ) { super( original ); this.propertyAccess = original.propertyAccess; + this.generator = original.getGenerator(); } @Override @@ -64,7 +70,7 @@ public PropertyAccess getPropertyAccess() { @Override public Generator getGenerator() { - return findContainingEntityMapping().getEntityPersister().getEntityMetamodel().getGenerators()[getStateArrayPosition()]; + return generator; } } 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 8f0160ba8930..da41017bacdc 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 @@ -11,6 +11,7 @@ import org.hibernate.engine.FetchStyle; import org.hibernate.engine.FetchTiming; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.generator.Generator; import org.hibernate.internal.util.IndexedConsumer; import org.hibernate.metamodel.mapping.AttributeMetadata; import org.hibernate.metamodel.mapping.BasicValuedModelPart; @@ -43,7 +44,7 @@ @SuppressWarnings("rawtypes") public class BasicAttributeMapping extends AbstractSingularAttributeMapping - implements SingularAttributeMapping, BasicValuedModelPart { + implements BasicValuedModelPart { private final NavigableRole navigableRole; private final String tableExpression; @@ -95,6 +96,65 @@ public BasicAttributeMapping( JdbcMapping jdbcMapping, ManagedMappingType declaringType, PropertyAccess propertyAccess) { + this( + attributeName, + navigableRole, + stateArrayPosition, + fetchableIndex, + attributeMetadata, + mappedFetchTiming, + mappedFetchStyle, + tableExpression, + mappedColumnExpression, + selectablePath, + isFormula, + customReadExpression, + customWriteExpression, + columnDefinition, + length, + precision, + scale, + temporalPrecision, + isLob, + nullable, + insertable, + updateable, + partitioned, + jdbcMapping, + declaringType, + null, + propertyAccess + ); + } + + public BasicAttributeMapping( + String attributeName, + NavigableRole navigableRole, + int stateArrayPosition, + int fetchableIndex, + AttributeMetadata attributeMetadata, + FetchTiming mappedFetchTiming, + FetchStyle mappedFetchStyle, + String tableExpression, + String mappedColumnExpression, + SelectablePath selectablePath, + boolean isFormula, + String customReadExpression, + String customWriteExpression, + String columnDefinition, + Long length, + Integer precision, + Integer scale, + Integer temporalPrecision, + boolean isLob, + boolean nullable, + boolean insertable, + boolean updateable, + boolean partitioned, + JdbcMapping jdbcMapping, + ManagedMappingType declaringType, + Generator generator, + PropertyAccess propertyAccess) { super( attributeName, stateArrayPosition, @@ -103,6 +163,7 @@ public BasicAttributeMapping( mappedFetchTiming, mappedFetchStyle, declaringType, + generator, propertyAccess ); this.navigableRole = navigableRole; diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedAssociationAttributeMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedAssociationAttributeMapping.java index 3c40af99c00d..6fbbbeccbf61 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedAssociationAttributeMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedAssociationAttributeMapping.java @@ -17,6 +17,7 @@ import org.hibernate.engine.FetchTiming; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.generator.Generator; import org.hibernate.internal.util.IndexedConsumer; import org.hibernate.mapping.Any; import org.hibernate.mapping.Property; @@ -79,6 +80,40 @@ public DiscriminatedAssociationAttributeMapping( AnyType anyType, Any bootValueMapping, MappingModelCreationProcess creationProcess) { + this( + attributeRole, + baseAssociationJtd, + declaringType, + stateArrayPosition, + fetchableIndex, + attributeMetadata, + fetchTiming, + propertyAccess, + bootProperty, + anyType, + bootValueMapping, + declaringType.findContainingEntityMapping() + .getEntityPersister() + .getEntityMetamodel() + .getGenerators()[stateArrayPosition], + creationProcess + ); + } + + public DiscriminatedAssociationAttributeMapping( + NavigableRole attributeRole, + JavaType baseAssociationJtd, + ManagedMappingType declaringType, + int stateArrayPosition, + int fetchableIndex, + AttributeMetadata attributeMetadata, + FetchTiming fetchTiming, + PropertyAccess propertyAccess, + Property bootProperty, + AnyType anyType, + Any bootValueMapping, + Generator generator, + MappingModelCreationProcess creationProcess) { super( bootProperty.getName(), stateArrayPosition, @@ -87,6 +122,7 @@ public DiscriminatedAssociationAttributeMapping( fetchTiming, FetchStyle.SELECT, declaringType, + generator, propertyAccess ); this.navigableRole = attributeRole; 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 0dfa4e526bab..7bef3f4cc24a 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; @@ -120,6 +123,7 @@ public static EmbeddableMappingTypeImpl from( 0, insertability, updateability, + null, embeddedPartBuilder, creationProcess ); @@ -135,6 +139,7 @@ public static EmbeddableMappingTypeImpl from( int dependantColumnIndex, boolean[] insertability, boolean[] updateability, + Generator generator, Function embeddedPartBuilder, MappingModelCreationProcess creationProcess) { final RuntimeModelCreationContext creationContext = creationProcess.getCreationContext(); @@ -162,6 +167,7 @@ public static EmbeddableMappingTypeImpl from( dependantColumnIndex, insertability, updateability, + generator, creationProcess ) ); @@ -406,6 +412,7 @@ private boolean finishInitialization( int dependantColumnIndex, boolean[] insertability, boolean[] updateability, + Generator generator, MappingModelCreationProcess creationProcess) { // for some reason I cannot get this to work, though only a single test fails - `CompositeElementTest` // return finishInitialization( @@ -433,8 +440,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(); @@ -446,13 +454,39 @@ private boolean finishInitialization( // Reset the attribute mappings that were added in previous attempts attributeMappings.clear(); + final OnExecutionCompositeGenerator onExecutionCompositeGenerator; + if ( generator instanceof OnExecutionCompositeGenerator ) { + onExecutionCompositeGenerator = (OnExecutionCompositeGenerator) generator; + } + else { + onExecutionCompositeGenerator = null; + } 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( bootPropertyDescriptor.getName() ); + } + 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 +497,7 @@ private boolean finishInitialization( if ( selectable.isFormula() ) { columnExpression = selectable.getTemplate( dialect, - creationProcess.getCreationContext().getTypeConfiguration(), + creationContext.getTypeConfiguration(), creationProcess.getSqmFunctionRegistry() ); } @@ -501,7 +535,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,10 +571,11 @@ private boolean finishInitialization( temporalPrecision, isLob, nullable, - insertability[columnPosition], - updateability[columnPosition], + insertable, + updateable, representationStrategy.resolvePropertyAccess( bootPropertyDescriptor ), compositeType.getCascadeStyle( attributeIndex ), + onExecutionGenerator, creationProcess ); @@ -552,11 +587,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 ), @@ -580,12 +623,13 @@ else if ( subtype instanceof AnyType ) { bootPropertyDescriptor, anyType, bootValueMapping, + onExecutionGenerator, creationProcess ); } 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 ) { @@ -611,6 +655,7 @@ else if ( subtype instanceof CompositeType ) { subRootTableKeyColumnNames, representationStrategy.resolvePropertyAccess( bootPropertyDescriptor ), compositeType.getCascadeStyle( attributeIndex ), + onExecutionGenerator, creationProcess ); @@ -643,6 +688,7 @@ else if ( subtype instanceof EntityType ) { (EntityType) subtype, representationStrategy.resolvePropertyAccess( bootPropertyDescriptor ), compositeType.getCascadeStyle( attributeIndex ), + onExecutionGenerator, creationProcess ); columnPosition += bootPropertyDescriptor.getColumnSpan(); @@ -1139,4 +1185,11 @@ public boolean shouldSelectAggregateMapping() { public boolean shouldBindAggregateMapping() { return preferBindAggregateMapping; } + + 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/EmbeddedAttributeMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedAttributeMapping.java index d8c1b3e70160..2caaf3a6c8a3 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 @@ -13,6 +13,8 @@ import org.hibernate.engine.FetchStyle; import org.hibernate.engine.FetchTiming; +import org.hibernate.generator.CompositeGenerator; +import org.hibernate.generator.Generator; import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.metamodel.mapping.AttributeMapping; import org.hibernate.metamodel.mapping.AttributeMetadata; @@ -24,6 +26,7 @@ import org.hibernate.metamodel.mapping.SelectableMapping; import org.hibernate.metamodel.mapping.SelectableMappings; import org.hibernate.metamodel.model.domain.NavigableRole; +import org.hibernate.persister.entity.AbstractEntityPersister; import org.hibernate.property.access.internal.PropertyAccessStrategyBasicImpl; import org.hibernate.property.access.spi.PropertyAccess; import org.hibernate.query.sqm.sql.SqmToSqlAstConverter; @@ -116,6 +119,7 @@ public EmbeddedAttributeMapping( mappedFetchTiming, mappedFetchStyle, declaringType, + getGenerator( stateArrayPosition, declaringType ), propertyAccess ); this.navigableRole = navigableRole; @@ -133,6 +137,24 @@ public EmbeddedAttributeMapping( } } + private static Generator getGenerator(int stateArrayPosition, ManagedMappingType declaringType) { + final Generator[] generators = declaringType.findContainingEntityMapping() + .getEntityPersister() + .getEntityMetamodel() + .getGenerators(); + if ( stateArrayPosition < 0 || generators == null ) { + return null; + } + if ( declaringType instanceof AbstractEntityPersister ) { + return generators[stateArrayPosition]; + } + else if ( declaringType.asAttributeMapping() != null && declaringType.asAttributeMapping().isEmbeddedAttributeMapping() ) { + final CompositeGenerator generator = (CompositeGenerator) generators[stateArrayPosition]; + return generator.getGenerator( declaringType.getPartName() ); + } + return null; + } + // Constructor is only used for creating the inverse attribute mapping EmbeddedAttributeMapping( ManagedMappingType keyDeclaringType, @@ -150,6 +172,7 @@ public EmbeddedAttributeMapping( : null, inverseModelPart.getMappedFetchOptions(), keyDeclaringType, + null, inverseModelPart instanceof PropertyBasedMapping ? ( (PropertyBasedMapping) inverseModelPart ).getPropertyAccess() : null diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/IdClassEmbeddable.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/IdClassEmbeddable.java index fb7cf26c7e1f..eb8de313b7dc 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/IdClassEmbeddable.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/IdClassEmbeddable.java @@ -353,6 +353,7 @@ private boolean finishInitialization( this::initColumnMappings ); }, + null, creationProcess ); } 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 ae46acbac96d..d801f1bf9713 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 @@ -29,6 +29,7 @@ import org.hibernate.engine.FetchTiming; import org.hibernate.engine.spi.CascadeStyle; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.generator.Generator; import org.hibernate.internal.util.StringHelper; import org.hibernate.mapping.AggregateColumn; import org.hibernate.mapping.Any; @@ -162,6 +163,7 @@ public static EntityIdentifierMapping buildEncapsulatedCompositeIdentifierMappin 0, component.getColumnInsertability(), component.getColumnUpdateability(), + null, embeddable -> new EmbeddedIdentifierMappingImpl( entityPersister, attributeName, @@ -220,6 +222,7 @@ public static BasicAttributeMapping buildBasicAttributeMapping( boolean insertable, boolean updateable, PropertyAccess propertyAccess, + Generator generator, CascadeStyle cascadeStyle, MappingModelCreationProcess creationProcess) { final SimpleValue value = (SimpleValue) bootProperty.getValue(); @@ -273,6 +276,90 @@ public static BasicAttributeMapping buildBasicAttributeMapping( partitioned, attrType, declaringType, + generator, + propertyAccess + ); + } + @SuppressWarnings("rawtypes") + public static BasicAttributeMapping buildBasicAttributeMapping( + String attrName, + NavigableRole navigableRole, + int stateArrayPosition, + int fetchableIndex, + Property bootProperty, + ManagedMappingType declaringType, + BasicType attrType, + String tableExpression, + String attrColumnName, + SelectablePath selectablePath, + boolean isAttrFormula, + String readExpr, + String writeExpr, + String columnDefinition, + Long length, + Integer precision, + Integer scale, + Integer temporalPrecision, + boolean isLob, + boolean nullable, + boolean insertable, + boolean updateable, + PropertyAccess propertyAccess, + CascadeStyle cascadeStyle, + Generator generator, + MappingModelCreationProcess creationProcess) { + final SimpleValue value = (SimpleValue) bootProperty.getValue(); + final BasicValue.Resolution resolution = ( (Resolvable) value ).resolve(); + final SimpleAttributeMetadata attributeMetadata = new SimpleAttributeMetadata( propertyAccess, resolution.getMutabilityPlan(), bootProperty, value ); + + final FetchTiming fetchTiming; + final FetchStyle fetchStyle; + final boolean partitioned; + if ( declaringType instanceof EmbeddableMappingType ) { + if ( bootProperty.isLazy() ) { + MAPPING_MODEL_CREATION_MESSAGE_LOGGER.debugf( + "Attribute was declared lazy, but is part of an embeddable - `%s#%s` - LAZY will be ignored", + declaringType.getNavigableRole().getFullPath(), + bootProperty.getName() + ); + } + fetchTiming = FetchTiming.IMMEDIATE; + fetchStyle = FetchStyle.JOIN; + partitioned = value.isPartitionKey() && !( (EmbeddableMappingType) declaringType ).getEmbeddedValueMapping().isVirtual(); + } + else { + fetchTiming = bootProperty.isLazy() ? FetchTiming.DELAYED : FetchTiming.IMMEDIATE; + fetchStyle = bootProperty.isLazy() ? FetchStyle.SELECT : FetchStyle.JOIN; + partitioned = value.isPartitionKey(); + } + + return new BasicAttributeMapping( + attrName, + navigableRole, + stateArrayPosition, + fetchableIndex, + attributeMetadata, + fetchTiming, + fetchStyle, + tableExpression, + attrColumnName, + selectablePath, + isAttrFormula, + readExpr, + writeExpr, + columnDefinition, + length, + precision, + scale, + temporalPrecision, + isLob, + nullable, + insertable, + updateable, + partitioned, + attrType, + declaringType, + generator, propertyAccess ); } @@ -288,6 +375,7 @@ public static EmbeddedAttributeMapping buildEmbeddedAttributeMapping( String[] rootTableKeyColumnNames, PropertyAccess propertyAccess, CascadeStyle cascadeStyle, + Generator generator, MappingModelCreationProcess creationProcess) { return buildEmbeddedAttributeMapping( attrName, @@ -302,6 +390,7 @@ public static EmbeddedAttributeMapping buildEmbeddedAttributeMapping( rootTableKeyColumnNames, propertyAccess, cascadeStyle, + generator, creationProcess ); } @@ -319,6 +408,7 @@ public static EmbeddedAttributeMapping buildEmbeddedAttributeMapping( String[] rootTableKeyColumnNames, PropertyAccess propertyAccess, CascadeStyle cascadeStyle, + Generator generator, MappingModelCreationProcess creationProcess) { final AttributeMetadata attributeMetadataAccess = getAttributeMetadata( bootProperty, @@ -344,6 +434,7 @@ public static EmbeddedAttributeMapping buildEmbeddedAttributeMapping( dependantColumnIndex, component.getColumnInsertability(), component.getColumnUpdateability(), + generator, attributeMappingType -> { if ( component.isEmbedded() ) { return new VirtualEmbeddedAttributeMapping( @@ -1361,7 +1452,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( @@ -1811,6 +1902,37 @@ public JavaType getMappedJavaType() { } } + + public static ToOneAttributeMapping buildSingularAssociationAttributeMapping( + String attrName, + NavigableRole navigableRole, + int stateArrayPosition, + int fetchableIndex, + Property bootProperty, + ManagedMappingType declaringType, + EntityPersister declaringEntityPersister, + EntityType attrType, + PropertyAccess propertyAccess, + CascadeStyle cascadeStyle, + Generator generator, + MappingModelCreationProcess creationProcess) { + return buildSingularAssociationAttributeMapping( + attrName, + navigableRole, + stateArrayPosition, + fetchableIndex, + bootProperty, + declaringType, + declaringEntityPersister, + attrType, + propertyAccess, + cascadeStyle, + creationProcess, + generator, + Function.identity() + ); + } + /** * For Hibernate Reactive */ @@ -1842,6 +1964,106 @@ public static ToOneAttributeMapping buildSingularAssociationAttributeMapping( ); } + public static ToOneAttributeMapping buildSingularAssociationAttributeMapping( + String attrName, + NavigableRole navigableRole, + int stateArrayPosition, + int fetchableIndex, + Property bootProperty, + ManagedMappingType declaringType, + EntityPersister declaringEntityPersister, + EntityType attrType, + PropertyAccess propertyAccess, + CascadeStyle cascadeStyle, + MappingModelCreationProcess creationProcess, + Generator generator, + Function mappingConverter) { + if ( bootProperty.getValue() instanceof ToOne ) { + final ToOne value = (ToOne) bootProperty.getValue(); + final EntityPersister entityPersister = creationProcess.getEntityPersister( value.getReferencedEntityName() ); + final AttributeMetadata attributeMetadata = getAttributeMetadata( + bootProperty, + attrType, + propertyAccess, + cascadeStyle, + creationProcess + ); + final SessionFactoryImplementor sessionFactory = creationProcess.getCreationContext().getSessionFactory(); + + final AssociationType type = (AssociationType) bootProperty.getType(); + final FetchStyle fetchStyle = FetchOptionsHelper + .determineFetchStyleByMetadata( + bootProperty.getValue().getFetchMode(), + type, + sessionFactory + ); + + final FetchTiming fetchTiming; + final String role = declaringType.getNavigableRole().toString() + "." + bootProperty.getName(); + final boolean lazy = value.isLazy(); + if ( lazy && entityPersister.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading() ) { + if ( value.isUnwrapProxy() ) { + fetchTiming = FetchOptionsHelper.determineFetchTiming( fetchStyle, type, lazy, role, sessionFactory ); + } + else if ( value instanceof ManyToOne && value.isNullable() && ( (ManyToOne) value ).isIgnoreNotFound() ) { + fetchTiming = FetchTiming.IMMEDIATE; + } + else { + fetchTiming = FetchOptionsHelper.determineFetchTiming( fetchStyle, type, lazy, role, sessionFactory ); + } + } + else if ( !lazy + || value instanceof OneToOne && value.isNullable() + || value instanceof ManyToOne && value.isNullable() && ( (ManyToOne) value ).isIgnoreNotFound() ) { + fetchTiming = FetchTiming.IMMEDIATE; + if ( lazy ) { + if ( MAPPING_MODEL_CREATION_MESSAGE_LOGGER.isDebugEnabled() ) { + MAPPING_MODEL_CREATION_MESSAGE_LOGGER.debugf( + "Forcing FetchTiming.IMMEDIATE for to-one association : %s.%s", + declaringType.getNavigableRole(), + bootProperty.getName() + ); + } + } + } + else { + fetchTiming = FetchOptionsHelper.determineFetchTiming( fetchStyle, type, lazy, role, sessionFactory ); + } + + final ToOneAttributeMapping attributeMapping = mappingConverter.apply( new ToOneAttributeMapping( + attrName, + navigableRole, + stateArrayPosition, + fetchableIndex, + (ToOne) bootProperty.getValue(), + attributeMetadata, + fetchTiming, + fetchStyle, + entityPersister, + declaringType, + declaringEntityPersister, + generator, + propertyAccess + ) ); + + creationProcess.registerForeignKeyPostInitCallbacks( + "To-one key - " + navigableRole, + () -> MappingModelCreationHelper.interpretToOneKeyDescriptor( + attributeMapping, + bootProperty, + (ToOne) bootProperty.getValue(), + null, + creationProcess.getCreationContext().getDialect(), + creationProcess + ) + ); + return attributeMapping; + } + else { + throw new UnsupportedOperationException( "AnyType support has not yet been implemented" ); + } + } + public static ToOneAttributeMapping buildSingularAssociationAttributeMapping( String attrName, NavigableRole navigableRole, @@ -1919,6 +2141,7 @@ else if ( !lazy entityPersister, declaringType, declaringEntityPersister, + declaringEntityPersister.getEntityMetamodel().getGenerators()[stateArrayPosition], propertyAccess ) ); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ToOneAttributeMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ToOneAttributeMapping.java index 3de7655467b4..884c77821614 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ToOneAttributeMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ToOneAttributeMapping.java @@ -23,6 +23,7 @@ import org.hibernate.engine.FetchTiming; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.generator.Generator; import org.hibernate.internal.util.IndexedConsumer; import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.mapping.Collection; @@ -200,7 +201,6 @@ protected ToOneAttributeMapping(ToOneAttributeMapping original) { sideNature = original.sideNature; identifyingColumnsTableExpression = original.identifyingColumnsTableExpression; canUseParentTableGroup = original.canUseParentTableGroup; - } public ToOneAttributeMapping( @@ -227,6 +227,7 @@ public ToOneAttributeMapping( entityMappingType, declaringType, declaringEntityPersister, + declaringEntityPersister.getEntityMetamodel().getGenerators()[stateArrayPosition], propertyAccess ); } @@ -243,6 +244,7 @@ public ToOneAttributeMapping( EntityMappingType entityMappingType, ManagedMappingType declaringType, EntityPersister declaringEntityPersister, + Generator generator, PropertyAccess propertyAccess) { super( name, @@ -252,6 +254,7 @@ public ToOneAttributeMapping( adjustFetchTiming( mappedFetchTiming, bootValue ), mappedFetchStyle, declaringType, + generator, propertyAccess ); this.sqlAliasStem = SqlAliasStemHelper.INSTANCE.generateStemFromAttributeName( name ); @@ -684,6 +687,7 @@ private ToOneAttributeMapping( original.getAttributeMetadata(), original, declaringType, + original.getGenerator(), original.getPropertyAccess() ); this.navigableRole = original.navigableRole; @@ -2569,4 +2573,5 @@ public int forEachJdbcValue( session ); } + } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/VirtualIdEmbeddable.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/VirtualIdEmbeddable.java index f5233d598c36..614e32d06e73 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/VirtualIdEmbeddable.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/VirtualIdEmbeddable.java @@ -245,6 +245,7 @@ private boolean finishInitialization( this::initColumnMappings ); }, + null, creationProcess ); } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index eefefb7b8bf9..554023127d96 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -5651,6 +5651,7 @@ protected AttributeMapping generateNonIdAttributeMapping( value.isColumnUpdateable( 0 ), propertyAccess, tupleAttrDefinition.getCascadeStyle(), + getEntityMetamodel().getGenerators()[stateArrayPosition], creationProcess ); } @@ -5753,6 +5754,7 @@ protected AttributeMapping generateNonIdAttributeMapping( value.isColumnUpdateable( 0 ), propertyAccess, tupleAttrDefinition.getCascadeStyle(), + getEntityMetamodel().getGenerators()[stateArrayPosition], creationProcess ); } @@ -5808,6 +5810,7 @@ else if ( attrType instanceof CompositeType ) { null, propertyAccess, tupleAttrDefinition.getCascadeStyle(), + getEntityMetamodel().getGenerators()[stateArrayPosition], creationProcess ); } @@ -5861,6 +5864,7 @@ protected EmbeddedAttributeMapping buildEmbeddedAttributeMapping( String[] rootTableKeyColumnNames, PropertyAccess propertyAccess, CascadeStyle cascadeStyle, + Generator generator, MappingModelCreationProcess creationProcess) { return MappingModelCreationHelper.buildEmbeddedAttributeMapping( attrName, @@ -5875,6 +5879,7 @@ protected EmbeddedAttributeMapping buildEmbeddedAttributeMapping( rootTableKeyColumnNames, propertyAccess, cascadeStyle, + generator, creationProcess ); } @@ -5905,6 +5910,7 @@ protected AttributeMapping buildSingularAssociationAttributeMapping( attrType, propertyAccess, cascadeStyle, + getEntityMetamodel().getGenerators()[stateArrayPosition], creationProcess ); } 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..b5c594a23bd0 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 @@ -222,7 +222,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 +230,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 +325,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 +382,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 +419,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 +430,33 @@ private void applyTableInsertDetails( final int attributeIndex = attributeIndexes[ i ]; final AttributeMapping attributeMapping = attributeMappings.get( attributeIndex ); if ( attributeInclusions[attributeIndex] ) { - attributeMapping.forEachInsertable( insertGroupBuilder ); + if ( attributeMapping.isEmbeddedAttributeMapping() ) { + attributeMapping.forEachInsertable( + (selectionIndex, selectableMapping) -> { + if ( selectableMapping instanceof AttributeMapping ) { + final AttributeMapping mapping = (AttributeMapping) selectableMapping; + Generator generator = mapping.getGenerator(); + 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 +500,106 @@ 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() ) { + attributeMapping.forEachInsertable( + (selectionIndex, selectableMapping) -> { + if ( selectableMapping instanceof AttributeMapping ) { + final AttributeMapping mapping = (AttributeMapping) selectableMapping; + final Object value = mapping.getValue( attributeValue ); + final Generator generator = mapping.getGenerator(); + 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..420f4742fe02 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,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 +852,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 +888,81 @@ private void decomposeAttributeForUpdate( } } + private void decomposeEmbeddedAttributeMapping( + Object value, + SharedSessionContractImplementor session, + JdbcValueBindings jdbcValueBindings, + EntityTableMapping tableMapping, + boolean isDynamicUpdate, + boolean isDirty, + AttributeMapping attributeMapping) { + 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 = singularAttributeMapping.getGenerator(); + 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 +1335,40 @@ 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() ) { + attributeMapping.forEachUpdatable( + (index, selectable) -> { + Generator gen = null; + if ( selectable instanceof AttributeMapping ) { + gen = ( (AttributeMapping) selectable ).getGenerator(); + } + if ( handleGeneratedValue( entity, session, gen ) ) { + handleUpdateValueGeneration( + (AttributeMapping) selectable, + updateGroupBuilder, + dirtinessChecker, + (OnExecutionGenerator) gen + ); + } + } + ); + final boolean includeInSet = !entityPersister().getEntityMetamodel().isDynamicUpdate() + || dirtinessChecker == null + || dirtinessChecker.isDirty( attributeIndex, attributeMapping ).isDirty(); + if ( includeInSet ) { + attributeMapping.forEachUpdatable( (index, selectable) -> { + Generator gen = null; + if ( selectable instanceof AttributeMapping ) { + gen = ( (AttributeMapping) selectable ).getGenerator(); + } + 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 +1384,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..ba35f52dfa96 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,18 +9,21 @@ 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.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; import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.persister.entity.EntityPersister; -import java.util.ArrayList; import java.util.EnumSet; +import java.util.HashMap; import java.util.List; +import java.util.Map; import static java.lang.System.arraycopy; import static org.hibernate.generator.EventTypeSets.NONE; @@ -36,7 +39,7 @@ class CompositeGeneratorBuilder { private boolean hadBeforeExecutionGeneration; private boolean hadOnExecutionGeneration; - private final List generators = new ArrayList<>(); + private final Map generators = new HashMap<>(); public CompositeGeneratorBuilder(String entityName, Property mappingProperty, Dialect dialect) { this.entityName = entityName; @@ -44,8 +47,8 @@ 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 ); if ( generator != null && generator.generatesSometimes() ) { if ( generator.generatedOnExecution() ) { @@ -84,68 +87,46 @@ public boolean generatedOnExecution() { } } - 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() ); + 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; + } } - arraycopy( referencedColumnValues, 0, columnValues, columnIndex, span ); - columnIndex += span; + if ( generator.writePropertyValue() ) { + writePropertyValue = true; + } + } + else { + generators.put( property.getName(), 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( generators, eventTypes, writePropertyValue, referenceColumnsInSql, columnValues ); } private BeforeExecutionGenerator createCompositeBeforeExecutionGenerator() { @@ -153,12 +134,12 @@ private BeforeExecutionGenerator createCompositeBeforeExecutionGenerator() { 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 +151,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 +162,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 +179,11 @@ public Object generate(SharedSessionContractImplementor session, Object owner, O public EnumSet getEventTypes() { return eventTypes; } + + @Override + public Generator getGenerator(String propertyName) { + return generators.get( propertyName ); + } }; } } 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 2ed7fdd3282c..f7ab134a1d77 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 @@ -325,7 +325,8 @@ public EntityMetamodel( } else { generators[i] = generator; - if ( generatedWithNoParameter( generator ) ) { + if ( !( attribute instanceof EntityBasedCompositionAttribute ) + && generatedWithNoParameter( generator ) ) { propertyInsertability[i] = false; propertyUpdateability[i] = false; } @@ -516,10 +517,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; + } + } +}