Skip to content

Commit 1eb8eaa

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

File tree

5 files changed

+121
-32
lines changed

5 files changed

+121
-32
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/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

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* SPDX-License-Identifier: LGPL-2.1-or-later
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.orm.test.mapping.generated.sqldefault;
6+
7+
import jakarta.persistence.Entity;
8+
import jakarta.persistence.Id;
9+
import org.hibernate.annotations.ColumnDefault;
10+
import org.hibernate.annotations.Generated;
11+
import org.hibernate.annotations.Immutable;
12+
import org.hibernate.testing.orm.junit.DomainModel;
13+
import org.hibernate.testing.orm.junit.SessionFactory;
14+
import org.hibernate.testing.orm.junit.SessionFactoryScope;
15+
import org.junit.jupiter.api.AfterEach;
16+
import org.junit.jupiter.api.Test;
17+
18+
import java.math.BigDecimal;
19+
20+
import static org.junit.Assert.assertEquals;
21+
22+
/**
23+
* @author Gavin King
24+
*/
25+
@SuppressWarnings("JUnitMalformedDeclaration")
26+
@DomainModel(annotatedClasses = ImmutableDefaultTest.OrderLine.class)
27+
@SessionFactory
28+
public class ImmutableDefaultTest {
29+
30+
@Test
31+
public void test(SessionFactoryScope scope) {
32+
BigDecimal unitPrice = new BigDecimal("12.99");
33+
scope.inTransaction( session -> {
34+
OrderLine entity = new OrderLine( unitPrice, 5 );
35+
session.persist(entity);
36+
session.flush();
37+
assertEquals( "new", entity.status );
38+
assertEquals( unitPrice, entity.unitPrice );
39+
assertEquals( 5, entity.quantity );
40+
} );
41+
scope.inTransaction( session -> {
42+
OrderLine entity = session.createQuery("from WithDefault", OrderLine.class ).getSingleResult();
43+
assertEquals( unitPrice, entity.unitPrice );
44+
assertEquals( 5, entity.quantity );
45+
assertEquals( "new", entity.status );
46+
entity.status = "old"; //should be ignored due to @Immutable
47+
} );
48+
scope.inTransaction( session -> {
49+
OrderLine entity = session.createQuery("from WithDefault", OrderLine.class ).getSingleResult();
50+
assertEquals( unitPrice, entity.unitPrice );
51+
assertEquals( 5, entity.quantity );
52+
assertEquals( "new", entity.status );
53+
} );
54+
}
55+
56+
@AfterEach
57+
public void dropTestData(SessionFactoryScope scope) {
58+
scope.inTransaction( session -> session.createQuery( "delete WithDefault" ).executeUpdate() );
59+
}
60+
61+
@Entity(name="WithDefault")
62+
public static class OrderLine {
63+
@Id
64+
private BigDecimal unitPrice;
65+
@Id @ColumnDefault(value = "1")
66+
private int quantity;
67+
@Generated @Immutable
68+
@ColumnDefault(value = "'new'")
69+
private String status;
70+
71+
public OrderLine() {}
72+
public OrderLine(BigDecimal unitPrice, int quantity) {
73+
this.unitPrice = unitPrice;
74+
this.quantity = quantity;
75+
}
76+
}
77+
}

0 commit comments

Comments
 (0)