HHH-18815 @Generated should not imply @Immutable
Signed-off-by: Gavin King <gavin@hibernate.org>
This commit is contained in:
parent
51254568df
commit
5fca1206b2
|
@ -34,6 +34,11 @@ public class Assigned implements Generator {
|
|||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allowMutation() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EnumSet<EventType> getEventTypes() {
|
||||
return EventTypeSets.NONE;
|
||||
|
|
|
@ -70,16 +70,7 @@ class CompositeGeneratorBuilder {
|
|||
return createCompositeOnExecutionGenerator();
|
||||
}
|
||||
else {
|
||||
return new Generator() {
|
||||
@Override
|
||||
public EnumSet<EventType> 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<EventType> 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<EventType> 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<EventType> getEventTypes() {
|
||||
return eventTypes;
|
||||
private record CompositeOnExecutionGenerator(
|
||||
EnumSet<EventType> 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<EventType> getEventTypes() {
|
||||
return eventTypes;
|
||||
}
|
||||
}
|
||||
|
||||
private record CompositeBeforeExecutionGenerator(
|
||||
String entityName,
|
||||
List<Generator> generators,
|
||||
Property mappingProperty,
|
||||
List<Property> properties,
|
||||
EnumSet<EventType> eventTypes)
|
||||
implements BeforeExecutionGenerator {
|
||||
@Override
|
||||
public EnumSet<EventType> 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<EventType> getEventTypes() {
|
||||
return NONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean generatedOnExecution() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allowMutation() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allowAssignedIdentifiers() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -235,9 +235,9 @@ public class EntityMetamodel implements Serializable {
|
|||
boolean foundUpdateableNaturalIdProperty = false;
|
||||
BeforeExecutionGenerator tempVersionGenerator = null;
|
||||
|
||||
List<Property> props = persistentClass.getPropertyClosure();
|
||||
final List<Property> props = persistentClass.getPropertyClosure();
|
||||
for ( int i=0; i<props.size(); i++ ) {
|
||||
Property property = props.get(i);
|
||||
final Property property = props.get(i);
|
||||
final NonIdentifierAttribute attribute;
|
||||
if ( property == persistentClass.getVersion() ) {
|
||||
tempVersionProperty = i;
|
||||
|
@ -310,9 +310,11 @@ public class EntityMetamodel implements Serializable {
|
|||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
// generated value strategies ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
final Generator generator = buildGenerator( name, property, creationContext );
|
||||
if ( generator != null ) {
|
||||
if ( i == tempVersionProperty && !generator.generatedOnExecution() ) {
|
||||
final boolean generatedOnExecution = generator.generatedOnExecution();
|
||||
if ( i == tempVersionProperty && !generatedOnExecution ) {
|
||||
// when we have an in-memory generator for the version, we
|
||||
// want to plug it in to the older infrastructure specific
|
||||
// to version generation, instead of treating it like a
|
||||
|
@ -321,33 +323,33 @@ public class EntityMetamodel implements Serializable {
|
|||
}
|
||||
else {
|
||||
generators[i] = generator;
|
||||
if ( !generator.allowMutation() ) {
|
||||
propertyInsertability[i] = false;
|
||||
propertyUpdateability[i] = false;
|
||||
final boolean allowMutation = generator.allowMutation();
|
||||
if ( !allowMutation ) {
|
||||
propertyCheckability[i] = false;
|
||||
}
|
||||
if ( generator.generatesOnInsert() ) {
|
||||
propertyInsertability[i] = !generatedWithNoParameter( generator );
|
||||
if ( generator.generatedOnExecution() ) {
|
||||
foundPostInsertGeneratedValues = true;
|
||||
if ( generator instanceof BeforeExecutionGenerator ) {
|
||||
foundPreInsertGeneratedValues = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
foundPreInsertGeneratedValues = true;
|
||||
if ( generatedOnExecution ) {
|
||||
propertyInsertability[i] = writePropertyValue( (OnExecutionGenerator) generator );
|
||||
}
|
||||
foundPostInsertGeneratedValues = foundPostInsertGeneratedValues
|
||||
|| generator instanceof OnExecutionGenerator;
|
||||
foundPreInsertGeneratedValues = foundPreInsertGeneratedValues
|
||||
|| generator instanceof BeforeExecutionGenerator;
|
||||
}
|
||||
else if ( !allowMutation ) {
|
||||
propertyInsertability[i] = false;
|
||||
}
|
||||
if ( generator.generatesOnUpdate() ) {
|
||||
propertyUpdateability[i] = !generatedWithNoParameter( generator );
|
||||
if ( generator.generatedOnExecution() ) {
|
||||
foundPostUpdateGeneratedValues = true;
|
||||
if ( generator instanceof BeforeExecutionGenerator ) {
|
||||
foundPreUpdateGeneratedValues = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
foundPreUpdateGeneratedValues = true;
|
||||
if ( generatedOnExecution ) {
|
||||
propertyUpdateability[i] = writePropertyValue( (OnExecutionGenerator) generator );
|
||||
}
|
||||
foundPostUpdateGeneratedValues = foundPostUpdateGeneratedValues
|
||||
|| generator instanceof OnExecutionGenerator;
|
||||
foundPreUpdateGeneratedValues = foundPreUpdateGeneratedValues
|
||||
|| generator instanceof BeforeExecutionGenerator;
|
||||
}
|
||||
else if ( !allowMutation ) {
|
||||
propertyUpdateability[i] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -472,6 +474,15 @@ public class EntityMetamodel implements Serializable {
|
|||
// entityNameByInheritanceClassMap = toSmallMap( entityNameByInheritanceClassMapLocal );
|
||||
}
|
||||
|
||||
private static boolean writePropertyValue(OnExecutionGenerator generator) {
|
||||
final boolean writePropertyValue = generator.writePropertyValue();
|
||||
// TODO: move this validation somewhere else!
|
||||
// if ( !writePropertyValue && generator instanceof BeforeExecutionGenerator ) {
|
||||
// throw new HibernateException( "BeforeExecutionGenerator returned false from OnExecutionGenerator.writePropertyValue()" );
|
||||
// }
|
||||
return writePropertyValue;
|
||||
}
|
||||
|
||||
private Generator buildIdGenerator(PersistentClass persistentClass, RuntimeModelCreationContext creationContext) {
|
||||
final Generator existing = creationContext.getGenerators().get( rootName );
|
||||
if ( existing != null ) {
|
||||
|
@ -513,11 +524,6 @@ public class EntityMetamodel implements Serializable {
|
|||
return getName() + "." + property.getName();
|
||||
}
|
||||
|
||||
private static boolean generatedWithNoParameter(Generator generator) {
|
||||
return generator.generatedOnExecution()
|
||||
&& !((OnExecutionGenerator) generator).writePropertyValue();
|
||||
}
|
||||
|
||||
private static Generator buildGenerator(
|
||||
final String entityName,
|
||||
final Property mappingProperty,
|
||||
|
|
|
@ -11,6 +11,7 @@ import jakarta.persistence.Id;
|
|||
import jakarta.persistence.Table;
|
||||
import org.hibernate.annotations.DialectOverride;
|
||||
import org.hibernate.annotations.Generated;
|
||||
import org.hibernate.annotations.Immutable;
|
||||
import org.hibernate.annotations.SQLInsert;
|
||||
import org.hibernate.annotations.SQLUpdate;
|
||||
import org.hibernate.dialect.H2Dialect;
|
||||
|
@ -82,7 +83,7 @@ public class CustomSqlOverrideTest {
|
|||
static class Custom {
|
||||
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
Long id;
|
||||
@Generated
|
||||
@Generated @Immutable
|
||||
String uid;
|
||||
String whatever;
|
||||
}
|
||||
|
|
|
@ -44,13 +44,13 @@ public class DefaultTest {
|
|||
assertEquals( unitPrice, entity.unitPrice );
|
||||
assertEquals( 5, entity.quantity );
|
||||
assertEquals( "new", entity.status );
|
||||
entity.status = "old"; //should be ignored when fetch=true
|
||||
entity.status = "old";
|
||||
} );
|
||||
scope.inTransaction( session -> {
|
||||
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 );
|
||||
} );
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
} );
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue