Skip to content

Commit 5fca120

Browse files
committed
HHH-18815 @generated should not imply @immutable
Signed-off-by: Gavin King <[email protected]>
1 parent 5125456 commit 5fca120

File tree

7 files changed

+219
-99
lines changed

7 files changed

+219
-99
lines changed

hibernate-core/src/main/java/org/hibernate/generator/Assigned.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ public boolean allowAssignedIdentifiers() {
3434
return true;
3535
}
3636

37+
@Override
38+
public boolean allowMutation() {
39+
return true;
40+
}
41+
3742
@Override
3843
public EnumSet<EventType> getEventTypes() {
3944
return EventTypeSets.NONE;

hibernate-core/src/main/java/org/hibernate/tuple/entity/CompositeGeneratorBuilder.java

Lines changed: 97 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -70,16 +70,7 @@ else if ( hadOnExecutionGeneration ) {
7070
return createCompositeOnExecutionGenerator();
7171
}
7272
else {
73-
return new Generator() {
74-
@Override
75-
public EnumSet<EventType> getEventTypes() {
76-
return NONE;
77-
}
78-
@Override
79-
public boolean generatedOnExecution() {
80-
return false;
81-
}
82-
};
73+
return DummyGenerator.INSTANCE;
8374
}
8475
}
8576

@@ -89,6 +80,8 @@ private OnExecutionGenerator createCompositeOnExecutionGenerator() {
8980
// the base-line values for the aggregated OnExecutionGenerator we will build here.
9081
final EnumSet<EventType> eventTypes = EnumSet.noneOf(EventType.class);
9182
boolean referenceColumns = false;
83+
boolean writable = false;
84+
boolean mutable = false;
9285
final String[] columnValues = new String[composite.getColumnSpan()];
9386

9487
// start building the aggregate values
@@ -120,31 +113,16 @@ private OnExecutionGenerator createCompositeOnExecutionGenerator() {
120113
columnIndex += span;
121114
}
122115
}
123-
}
124-
final boolean referenceColumnsInSql = referenceColumns;
125-
126-
// then use the aggregated values to build an OnExecutionGenerator
127-
return new OnExecutionGenerator() {
128-
@Override
129-
public EnumSet<EventType> getEventTypes() {
130-
return eventTypes;
116+
if ( generator.writePropertyValue() ) {
117+
writable = true;
131118
}
132-
133-
@Override
134-
public boolean referenceColumnsInSql(Dialect dialect) {
135-
return referenceColumnsInSql;
136-
}
137-
138-
@Override
139-
public String[] getReferencedColumnValues(Dialect dialect) {
140-
return columnValues;
119+
if ( generator.allowMutation() ) {
120+
mutable = true;
141121
}
122+
}
142123

143-
@Override
144-
public boolean writePropertyValue() {
145-
return false;
146-
}
147-
};
124+
// then use the aggregated values to build an OnExecutionGenerator
125+
return new CompositeOnExecutionGenerator( eventTypes, referenceColumns, columnValues, writable, mutable );
148126
}
149127

150128
private BeforeExecutionGenerator createCompositeBeforeExecutionGenerator() {
@@ -157,45 +135,98 @@ private BeforeExecutionGenerator createCompositeBeforeExecutionGenerator() {
157135
eventTypes.addAll( generator.getEventTypes() );
158136
}
159137
}
160-
return new BeforeExecutionGenerator() {
161-
@Override
162-
public Object generate(SharedSessionContractImplementor session, Object owner, Object currentValue, EventType eventType) {
163-
final EntityPersister persister = session.getEntityPersister( entityName, owner );
164-
final int index = persister.getPropertyIndex( mappingProperty.getName() );
165-
final EmbeddableMappingType descriptor =
166-
persister.getAttributeMapping(index).asEmbeddedAttributeMapping()
167-
.getEmbeddableTypeDescriptor();
168-
final int size = properties.size();
169-
if ( currentValue == null ) {
170-
final Object[] generatedValues = new Object[size];
171-
for ( int i = 0; i < size; i++ ) {
172-
final Generator generator = generators.get(i);
173-
if ( generator != null ) {
174-
generatedValues[i] = ((BeforeExecutionGenerator) generator)
175-
.generate( session, owner, null, eventType );
176-
}
138+
return new CompositeBeforeExecutionGenerator( entityName, generators, mappingProperty, properties, eventTypes );
139+
}
140+
141+
private record CompositeOnExecutionGenerator(
142+
EnumSet<EventType> eventTypes,
143+
boolean referenceColumnsInSql,
144+
String[] columnValues,
145+
boolean writePropertyValue,
146+
boolean allowMutation)
147+
implements OnExecutionGenerator {
148+
@Override
149+
public boolean referenceColumnsInSql(Dialect dialect) {
150+
return referenceColumnsInSql;
151+
}
152+
@Override
153+
public String[] getReferencedColumnValues(Dialect dialect) {
154+
return columnValues;
155+
}
156+
157+
@Override
158+
public EnumSet<EventType> getEventTypes() {
159+
return eventTypes;
160+
}
161+
}
162+
163+
private record CompositeBeforeExecutionGenerator(
164+
String entityName,
165+
List<Generator> generators,
166+
Property mappingProperty,
167+
List<Property> properties,
168+
EnumSet<EventType> eventTypes)
169+
implements BeforeExecutionGenerator {
170+
@Override
171+
public EnumSet<EventType> getEventTypes() {
172+
return eventTypes;
173+
}
174+
@Override
175+
public Object generate(SharedSessionContractImplementor session, Object owner, Object currentValue, EventType eventType) {
176+
final EntityPersister persister = session.getEntityPersister( entityName, owner );
177+
final int index = persister.getPropertyIndex( mappingProperty.getName() );
178+
final EmbeddableMappingType descriptor =
179+
persister.getAttributeMapping( index ).asEmbeddedAttributeMapping()
180+
.getEmbeddableTypeDescriptor();
181+
final int size = properties.size();
182+
if ( currentValue == null ) {
183+
final Object[] generatedValues = new Object[size];
184+
for ( int i = 0; i < size; i++ ) {
185+
final Generator generator = generators.get( i );
186+
if ( generator != null ) {
187+
generatedValues[i] = ((BeforeExecutionGenerator) generator)
188+
.generate( session, owner, null, eventType );
177189
}
178-
return descriptor.getRepresentationStrategy().getInstantiator()
179-
.instantiate( () -> generatedValues, session.getFactory() );
180190
}
181-
else {
182-
for ( int i = 0; i < size; i++ ) {
183-
final Generator generator = generators.get(i);
184-
if ( generator != null ) {
185-
final Object value = descriptor.getValue( currentValue, i );
186-
final Object generatedValue = ((BeforeExecutionGenerator) generator)
187-
.generate( session, owner, value, eventType );
188-
descriptor.setValue( currentValue, i, generatedValue );
189-
}
191+
return descriptor.getRepresentationStrategy().getInstantiator()
192+
.instantiate( () -> generatedValues, session.getFactory() );
193+
}
194+
else {
195+
for ( int i = 0; i < size; i++ ) {
196+
final Generator generator = generators.get( i );
197+
if ( generator != null ) {
198+
final Object value = descriptor.getValue( currentValue, i );
199+
final Object generatedValue = ((BeforeExecutionGenerator) generator)
200+
.generate( session, owner, value, eventType );
201+
descriptor.setValue( currentValue, i, generatedValue );
190202
}
191-
return currentValue;
192203
}
204+
return currentValue;
193205
}
206+
}
207+
}
194208

195-
@Override
196-
public EnumSet<EventType> getEventTypes() {
197-
return eventTypes;
198-
}
199-
};
209+
private record DummyGenerator() implements Generator {
210+
private static final Generator INSTANCE = new DummyGenerator();
211+
212+
@Override
213+
public EnumSet<EventType> getEventTypes() {
214+
return NONE;
215+
}
216+
217+
@Override
218+
public boolean generatedOnExecution() {
219+
return false;
220+
}
221+
222+
@Override
223+
public boolean allowMutation() {
224+
return true;
225+
}
226+
227+
@Override
228+
public boolean allowAssignedIdentifiers() {
229+
return true;
230+
}
200231
}
201232
}

hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -235,9 +235,9 @@ public EntityMetamodel(
235235
boolean foundUpdateableNaturalIdProperty = false;
236236
BeforeExecutionGenerator tempVersionGenerator = null;
237237

238-
List<Property> props = persistentClass.getPropertyClosure();
238+
final List<Property> props = persistentClass.getPropertyClosure();
239239
for ( int i=0; i<props.size(); i++ ) {
240-
Property property = props.get(i);
240+
final Property property = props.get(i);
241241
final NonIdentifierAttribute attribute;
242242
if ( property == persistentClass.getVersion() ) {
243243
tempVersionProperty = i;
@@ -310,9 +310,11 @@ public EntityMetamodel(
310310
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
311311

312312
// generated value strategies ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
313+
313314
final Generator generator = buildGenerator( name, property, creationContext );
314315
if ( generator != null ) {
315-
if ( i == tempVersionProperty && !generator.generatedOnExecution() ) {
316+
final boolean generatedOnExecution = generator.generatedOnExecution();
317+
if ( i == tempVersionProperty && !generatedOnExecution ) {
316318
// when we have an in-memory generator for the version, we
317319
// want to plug it in to the older infrastructure specific
318320
// to version generation, instead of treating it like a
@@ -321,33 +323,33 @@ public EntityMetamodel(
321323
}
322324
else {
323325
generators[i] = generator;
324-
if ( !generator.allowMutation() ) {
325-
propertyInsertability[i] = false;
326-
propertyUpdateability[i] = false;
326+
final boolean allowMutation = generator.allowMutation();
327+
if ( !allowMutation ) {
328+
propertyCheckability[i] = false;
327329
}
328330
if ( generator.generatesOnInsert() ) {
329-
propertyInsertability[i] = !generatedWithNoParameter( generator );
330-
if ( generator.generatedOnExecution() ) {
331-
foundPostInsertGeneratedValues = true;
332-
if ( generator instanceof BeforeExecutionGenerator ) {
333-
foundPreInsertGeneratedValues = true;
334-
}
335-
}
336-
else {
337-
foundPreInsertGeneratedValues = true;
331+
if ( generatedOnExecution ) {
332+
propertyInsertability[i] = writePropertyValue( (OnExecutionGenerator) generator );
338333
}
334+
foundPostInsertGeneratedValues = foundPostInsertGeneratedValues
335+
|| generator instanceof OnExecutionGenerator;
336+
foundPreInsertGeneratedValues = foundPreInsertGeneratedValues
337+
|| generator instanceof BeforeExecutionGenerator;
338+
}
339+
else if ( !allowMutation ) {
340+
propertyInsertability[i] = false;
339341
}
340342
if ( generator.generatesOnUpdate() ) {
341-
propertyUpdateability[i] = !generatedWithNoParameter( generator );
342-
if ( generator.generatedOnExecution() ) {
343-
foundPostUpdateGeneratedValues = true;
344-
if ( generator instanceof BeforeExecutionGenerator ) {
345-
foundPreUpdateGeneratedValues = true;
346-
}
347-
}
348-
else {
349-
foundPreUpdateGeneratedValues = true;
343+
if ( generatedOnExecution ) {
344+
propertyUpdateability[i] = writePropertyValue( (OnExecutionGenerator) generator );
350345
}
346+
foundPostUpdateGeneratedValues = foundPostUpdateGeneratedValues
347+
|| generator instanceof OnExecutionGenerator;
348+
foundPreUpdateGeneratedValues = foundPreUpdateGeneratedValues
349+
|| generator instanceof BeforeExecutionGenerator;
350+
}
351+
else if ( !allowMutation ) {
352+
propertyUpdateability[i] = false;
351353
}
352354
}
353355
}
@@ -472,6 +474,15 @@ && isAbstractClass( persistentClass.getMappedClass() ) ) {
472474
// entityNameByInheritanceClassMap = toSmallMap( entityNameByInheritanceClassMapLocal );
473475
}
474476

477+
private static boolean writePropertyValue(OnExecutionGenerator generator) {
478+
final boolean writePropertyValue = generator.writePropertyValue();
479+
// TODO: move this validation somewhere else!
480+
// if ( !writePropertyValue && generator instanceof BeforeExecutionGenerator ) {
481+
// throw new HibernateException( "BeforeExecutionGenerator returned false from OnExecutionGenerator.writePropertyValue()" );
482+
// }
483+
return writePropertyValue;
484+
}
485+
475486
private Generator buildIdGenerator(PersistentClass persistentClass, RuntimeModelCreationContext creationContext) {
476487
final Generator existing = creationContext.getGenerators().get( rootName );
477488
if ( existing != null ) {
@@ -513,11 +524,6 @@ private String propertyName(Property property) {
513524
return getName() + "." + property.getName();
514525
}
515526

516-
private static boolean generatedWithNoParameter(Generator generator) {
517-
return generator.generatedOnExecution()
518-
&& !((OnExecutionGenerator) generator).writePropertyValue();
519-
}
520-
521527
private static Generator buildGenerator(
522528
final String entityName,
523529
final Property mappingProperty,

hibernate-core/src/test/java/org/hibernate/orm/test/customsql/CustomSqlOverrideTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import jakarta.persistence.Table;
1212
import org.hibernate.annotations.DialectOverride;
1313
import org.hibernate.annotations.Generated;
14+
import org.hibernate.annotations.Immutable;
1415
import org.hibernate.annotations.SQLInsert;
1516
import org.hibernate.annotations.SQLUpdate;
1617
import org.hibernate.dialect.H2Dialect;
@@ -82,7 +83,7 @@ public void testCustomSql(SessionFactoryScope scope) {
8283
static class Custom {
8384
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
8485
Long id;
85-
@Generated
86+
@Generated @Immutable
8687
String uid;
8788
String whatever;
8889
}

hibernate-core/src/test/java/org/hibernate/orm/test/mapping/generated/sqldefault/DefaultTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,13 @@ public void test(SessionFactoryScope scope) {
4444
assertEquals( unitPrice, entity.unitPrice );
4545
assertEquals( 5, entity.quantity );
4646
assertEquals( "new", entity.status );
47-
entity.status = "old"; //should be ignored when fetch=true
47+
entity.status = "old";
4848
} );
4949
scope.inTransaction( session -> {
5050
OrderLine entity = session.createQuery("from WithDefault", OrderLine.class ).getSingleResult();
5151
assertEquals( unitPrice, entity.unitPrice );
5252
assertEquals( 5, entity.quantity );
53-
assertEquals( "new", entity.status );
53+
assertEquals( "old", entity.status );
5454
} );
5555
}
5656

0 commit comments

Comments
 (0)