diff --git a/hibernate-core/src/main/java/org/hibernate/generator/Assigned.java b/hibernate-core/src/main/java/org/hibernate/generator/Assigned.java index 3d4b52a111..dc81e0fe6a 100644 --- a/hibernate-core/src/main/java/org/hibernate/generator/Assigned.java +++ b/hibernate-core/src/main/java/org/hibernate/generator/Assigned.java @@ -34,6 +34,11 @@ public class Assigned implements Generator { return true; } + @Override + public boolean allowMutation() { + return true; + } + @Override public EnumSet getEventTypes() { return EventTypeSets.NONE; 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 bb2a24c673..04e7bc5736 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 @@ -70,16 +70,7 @@ class CompositeGeneratorBuilder { return createCompositeOnExecutionGenerator(); } else { - return new Generator() { - @Override - public EnumSet getEventTypes() { - return NONE; - } - @Override - public boolean generatedOnExecution() { - return false; - } - }; + return DummyGenerator.INSTANCE; } } @@ -89,6 +80,8 @@ class CompositeGeneratorBuilder { // the base-line values for the aggregated OnExecutionGenerator we will build here. final EnumSet eventTypes = EnumSet.noneOf(EventType.class); boolean referenceColumns = false; + boolean writable = false; + boolean mutable = false; final String[] columnValues = new String[composite.getColumnSpan()]; // start building the aggregate values @@ -120,31 +113,16 @@ class CompositeGeneratorBuilder { columnIndex += span; } } + if ( generator.writePropertyValue() ) { + writable = true; + } + if ( generator.allowMutation() ) { + mutable = true; + } } - 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 CompositeOnExecutionGenerator( eventTypes, referenceColumns, columnValues, writable, mutable ); } private BeforeExecutionGenerator createCompositeBeforeExecutionGenerator() { @@ -157,45 +135,98 @@ class CompositeGeneratorBuilder { eventTypes.addAll( generator.getEventTypes() ); } } - return new BeforeExecutionGenerator() { - @Override - public Object generate(SharedSessionContractImplementor session, Object owner, Object currentValue, EventType eventType) { - final EntityPersister persister = session.getEntityPersister( entityName, owner ); - final int index = persister.getPropertyIndex( mappingProperty.getName() ); - final EmbeddableMappingType descriptor = - persister.getAttributeMapping(index).asEmbeddedAttributeMapping() - .getEmbeddableTypeDescriptor(); - final int size = properties.size(); - if ( currentValue == null ) { - final Object[] generatedValues = new Object[size]; - for ( int i = 0; i < size; i++ ) { - final Generator generator = generators.get(i); - if ( generator != null ) { - generatedValues[i] = ((BeforeExecutionGenerator) generator) - .generate( session, owner, null, eventType ); - } - } - return descriptor.getRepresentationStrategy().getInstantiator() - .instantiate( () -> generatedValues, session.getFactory() ); - } - else { - for ( int i = 0; i < size; i++ ) { - final Generator generator = generators.get(i); - if ( generator != null ) { - final Object value = descriptor.getValue( currentValue, i ); - final Object generatedValue = ((BeforeExecutionGenerator) generator) - .generate( session, owner, value, eventType ); - descriptor.setValue( currentValue, i, generatedValue ); - } - } - return currentValue; - } - } + return new CompositeBeforeExecutionGenerator( entityName, generators, mappingProperty, properties, eventTypes ); + } - @Override - public EnumSet getEventTypes() { - return eventTypes; + private record CompositeOnExecutionGenerator( + EnumSet eventTypes, + boolean referenceColumnsInSql, + String[] columnValues, + boolean writePropertyValue, + boolean allowMutation) + implements OnExecutionGenerator { + @Override + public boolean referenceColumnsInSql(Dialect dialect) { + return referenceColumnsInSql; + } + @Override + public String[] getReferencedColumnValues(Dialect dialect) { + return columnValues; + } + + @Override + public EnumSet getEventTypes() { + return eventTypes; + } + } + + private record CompositeBeforeExecutionGenerator( + String entityName, + List generators, + Property mappingProperty, + List properties, + EnumSet eventTypes) + implements BeforeExecutionGenerator { + @Override + public EnumSet getEventTypes() { + return eventTypes; + } + @Override + public Object generate(SharedSessionContractImplementor session, Object owner, Object currentValue, EventType eventType) { + final EntityPersister persister = session.getEntityPersister( entityName, owner ); + final int index = persister.getPropertyIndex( mappingProperty.getName() ); + final EmbeddableMappingType descriptor = + persister.getAttributeMapping( index ).asEmbeddedAttributeMapping() + .getEmbeddableTypeDescriptor(); + final int size = properties.size(); + if ( currentValue == null ) { + final Object[] generatedValues = new Object[size]; + for ( int i = 0; i < size; i++ ) { + final Generator generator = generators.get( i ); + if ( generator != null ) { + generatedValues[i] = ((BeforeExecutionGenerator) generator) + .generate( session, owner, null, eventType ); + } + } + return descriptor.getRepresentationStrategy().getInstantiator() + .instantiate( () -> generatedValues, session.getFactory() ); } - }; + else { + for ( int i = 0; i < size; i++ ) { + final Generator generator = generators.get( i ); + if ( generator != null ) { + final Object value = descriptor.getValue( currentValue, i ); + final Object generatedValue = ((BeforeExecutionGenerator) generator) + .generate( session, owner, value, eventType ); + descriptor.setValue( currentValue, i, generatedValue ); + } + } + return currentValue; + } + } + } + + private record DummyGenerator() implements Generator { + private static final Generator INSTANCE = new DummyGenerator(); + + @Override + public EnumSet getEventTypes() { + return NONE; + } + + @Override + public boolean generatedOnExecution() { + return false; + } + + @Override + public boolean allowMutation() { + return true; + } + + @Override + public boolean allowAssignedIdentifiers() { + return true; + } } } 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 8e9146e2c0..f23eb84c39 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 @@ -235,9 +235,9 @@ public class EntityMetamodel implements Serializable { boolean foundUpdateableNaturalIdProperty = false; BeforeExecutionGenerator tempVersionGenerator = null; - List props = persistentClass.getPropertyClosure(); + final List props = persistentClass.getPropertyClosure(); for ( int i=0; i { OrderLine entity = session.createQuery("from WithDefault", OrderLine.class ).getSingleResult(); assertEquals( unitPrice, entity.unitPrice ); assertEquals( 5, entity.quantity ); - assertEquals( "new", entity.status ); + assertEquals( "old", entity.status ); } ); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/generated/sqldefault/ImmutableDefaultTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/generated/sqldefault/ImmutableDefaultTest.java new file mode 100644 index 0000000000..a251c1aac7 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/generated/sqldefault/ImmutableDefaultTest.java @@ -0,0 +1,77 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.mapping.generated.sqldefault; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import org.hibernate.annotations.ColumnDefault; +import org.hibernate.annotations.Generated; +import org.hibernate.annotations.Immutable; +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.AfterEach; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; + +import static org.junit.Assert.assertEquals; + +/** + * @author Gavin King + */ +@SuppressWarnings("JUnitMalformedDeclaration") +@DomainModel(annotatedClasses = ImmutableDefaultTest.OrderLine.class) +@SessionFactory +public class ImmutableDefaultTest { + + @Test + public void test(SessionFactoryScope scope) { + BigDecimal unitPrice = new BigDecimal("12.99"); + scope.inTransaction( session -> { + OrderLine entity = new OrderLine( unitPrice, 5 ); + session.persist(entity); + session.flush(); + assertEquals( "new", entity.status ); + assertEquals( unitPrice, entity.unitPrice ); + assertEquals( 5, entity.quantity ); + } ); + scope.inTransaction( session -> { + OrderLine entity = session.createQuery("from WithDefault", OrderLine.class ).getSingleResult(); + assertEquals( unitPrice, entity.unitPrice ); + assertEquals( 5, entity.quantity ); + assertEquals( "new", entity.status ); + entity.status = "old"; //should be ignored due to @Immutable + } ); + scope.inTransaction( session -> { + OrderLine entity = session.createQuery("from WithDefault", OrderLine.class ).getSingleResult(); + assertEquals( unitPrice, entity.unitPrice ); + assertEquals( 5, entity.quantity ); + assertEquals( "new", entity.status ); + } ); + } + + @AfterEach + public void dropTestData(SessionFactoryScope scope) { + scope.inTransaction( session -> session.createQuery( "delete WithDefault" ).executeUpdate() ); + } + + @Entity(name="WithDefault") + public static class OrderLine { + @Id + private BigDecimal unitPrice; + @Id @ColumnDefault(value = "1") + private int quantity; + @Generated @Immutable + @ColumnDefault(value = "'new'") + private String status; + + public OrderLine() {} + public OrderLine(BigDecimal unitPrice, int quantity) { + this.unitPrice = unitPrice; + this.quantity = quantity; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/generated/sqldefault/OverriddenDefaultTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/generated/sqldefault/OverriddenDefaultTest.java index 8600a90ed8..e11a41a2a6 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/generated/sqldefault/OverriddenDefaultTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/generated/sqldefault/OverriddenDefaultTest.java @@ -52,7 +52,7 @@ public class OverriddenDefaultTest { OrderLine entity = session.createQuery("from WithDefault", OrderLine.class ).getSingleResult(); assertEquals( unitPrice, entity.unitPrice ); assertEquals( 5, entity.quantity ); - assertEquals( getDefault(scope), entity.status ); + assertEquals( "old", entity.status ); } ); }