Skip to content

Commit ad0fe6b

Browse files
committed
HHH-6044 IDENTITY generation in composite ids
for both @IdClass and @EmbeddedId
1 parent c9e3d3c commit ad0fe6b

File tree

8 files changed

+274
-267
lines changed

8 files changed

+274
-267
lines changed

hibernate-core/src/main/java/org/hibernate/boot/model/internal/GeneratorBinder.java

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,33 +20,46 @@
2020
import org.hibernate.boot.models.spi.GlobalRegistrar;
2121
import org.hibernate.boot.spi.MetadataBuildingContext;
2222
import org.hibernate.boot.spi.PropertyData;
23+
import org.hibernate.dialect.Dialect;
2324
import org.hibernate.engine.config.spi.ConfigurationService;
2425
import org.hibernate.generator.AnnotationBasedGenerator;
2526
import org.hibernate.generator.Assigned;
2627
import org.hibernate.generator.BeforeExecutionGenerator;
28+
import org.hibernate.generator.EventType;
2729
import org.hibernate.generator.Generator;
2830
import org.hibernate.generator.GeneratorCreationContext;
2931
import org.hibernate.generator.OnExecutionGenerator;
32+
import org.hibernate.id.CompositeNestedGeneratedValueGenerator;
33+
import org.hibernate.id.CompositeNestedGeneratedValueGenerator.GenerationPlan;
3034
import org.hibernate.id.Configurable;
35+
import org.hibernate.id.IdentifierGenerationException;
3136
import org.hibernate.id.IdentifierGenerator;
3237
import org.hibernate.id.IdentityGenerator;
3338
import org.hibernate.id.PersistentIdentifierGenerator;
3439
import org.hibernate.id.enhanced.SequenceStyleGenerator;
3540
import org.hibernate.id.uuid.UuidValueGenerator;
41+
import org.hibernate.mapping.Component;
3642
import org.hibernate.mapping.GeneratorCreator;
43+
import org.hibernate.mapping.GeneratorSettings;
3744
import org.hibernate.mapping.KeyValue;
3845
import org.hibernate.mapping.PersistentClass;
46+
import org.hibernate.mapping.Property;
47+
import org.hibernate.mapping.RootClass;
3948
import org.hibernate.mapping.SimpleValue;
4049
import org.hibernate.mapping.Value;
4150
import org.hibernate.models.spi.AnnotationTarget;
4251
import org.hibernate.models.spi.MemberDetails;
52+
import org.hibernate.property.access.spi.Setter;
4353
import org.hibernate.resource.beans.container.spi.BeanContainer;
4454
import org.hibernate.resource.beans.internal.Helper;
55+
import org.hibernate.type.ComponentType;
4556

4657
import java.lang.annotation.Annotation;
4758
import java.lang.reflect.InvocationTargetException;
4859
import java.lang.reflect.Member;
60+
import java.util.ArrayList;
4961
import java.util.HashMap;
62+
import java.util.List;
5063
import java.util.Locale;
5164
import java.util.Map;
5265
import java.util.Properties;
@@ -903,4 +916,161 @@ public static void applyIfNotEmpty(String name, String value, BiConsumer<String,
903916
consumer.accept( name, value );
904917
}
905918
}
919+
920+
private static Setter injector(Property property, Class<?> attributeDeclarer) {
921+
return property.getPropertyAccessStrategy( attributeDeclarer )
922+
.buildPropertyAccess( attributeDeclarer, property.getName(), true )
923+
.getSetter();
924+
}
925+
926+
/**
927+
* Return the class that declares the composite pk attributes,
928+
* which might be an {@code @IdClass}, an {@code @EmbeddedId},
929+
* of the entity class itself.
930+
*/
931+
private static Class<?> getAttributeDeclarer(RootClass rootClass, Component component) {
932+
// See the javadoc discussion on CompositeNestedGeneratedValueGenerator
933+
// for the various scenarios we need to account for here
934+
if ( rootClass.getIdentifierMapper() != null ) {
935+
// we have the @IdClass / <composite-id mapped="true"/> case
936+
return resolveComponentClass( component );
937+
}
938+
else if ( rootClass.getIdentifierProperty() != null ) {
939+
// we have the "@EmbeddedId" / <composite-id name="idName"/> case
940+
return resolveComponentClass( component );
941+
}
942+
else {
943+
// we have the "straight up" embedded (again the Hibernate term)
944+
// component identifier: the entity class itself is the id class
945+
return rootClass.getMappedClass();
946+
}
947+
}
948+
949+
private static Class<?> resolveComponentClass(Component component) {
950+
try {
951+
return component.getComponentClass();
952+
}
953+
catch ( Exception e ) {
954+
return null;
955+
}
956+
}
957+
958+
public static Generator buildIdentifierGenerator(
959+
Component component,
960+
Dialect dialect,
961+
RootClass rootClass,
962+
GeneratorSettings defaults) {
963+
final var properties = component.getProperties();
964+
final List<Generator> generators = new ArrayList<>( properties.size() );
965+
final int columnSpan = component.getColumnSpan();
966+
String[] columnValues = null;
967+
boolean[] columnInclusions = null;
968+
boolean[] generatedOnExecutionColumns = null;
969+
int columnIndex = 0;
970+
final List<GenerationPlan> generationPlans = new ArrayList<>();
971+
for ( int i = 0; i < properties.size(); i++ ) {
972+
final var property = properties.get( i );
973+
final var propertyGenerator =
974+
propertyGenerator( component, dialect, rootClass, defaults, property, generationPlans, i );
975+
generators.add( propertyGenerator );
976+
977+
final int span = property.getColumnSpan();
978+
if ( propertyGenerator instanceof OnExecutionGenerator onExecutionGenerator
979+
&& propertyGenerator.generatedOnExecution() ) {
980+
if ( columnValues == null ) {
981+
columnValues = new String[columnSpan];
982+
columnInclusions = new boolean[columnSpan];
983+
generatedOnExecutionColumns = new boolean[columnSpan];
984+
for ( int j = 0; j < columnSpan; j++ ) {
985+
columnValues[j] = "?";
986+
columnInclusions[j] = true;
987+
}
988+
}
989+
for ( int j = 0; j < span; j++ ) {
990+
generatedOnExecutionColumns[columnIndex + j] = true;
991+
}
992+
if ( onExecutionGenerator.generatesOnInsert() ) {
993+
if ( !onExecutionGenerator.referenceColumnsInSql( dialect, EventType.INSERT ) ) {
994+
for ( int j = 0; j < span; j++ ) {
995+
columnInclusions[columnIndex + j] = false;
996+
}
997+
}
998+
else if ( onExecutionGenerator.writePropertyValue( EventType.INSERT ) ) {
999+
// leave default parameter markers in place
1000+
}
1001+
else {
1002+
final String[] referencedColumnValues =
1003+
onExecutionGenerator.getReferencedColumnValues( dialect, EventType.INSERT );
1004+
if ( referencedColumnValues == null ) {
1005+
throw new IdentifierGenerationException(
1006+
"Generated column values were not provided for composite id property: "
1007+
+ property.getName()
1008+
);
1009+
}
1010+
if ( referencedColumnValues.length != span ) {
1011+
throw new IdentifierGenerationException(
1012+
"Mismatch between generated column values and column count for composite id property: "
1013+
+ property.getName()
1014+
);
1015+
}
1016+
System.arraycopy( referencedColumnValues, 0, columnValues, columnIndex, span );
1017+
}
1018+
}
1019+
else if ( !onExecutionGenerator.allowMutation() ) {
1020+
for ( int j = 0; j < span; j++ ) {
1021+
columnInclusions[columnIndex + j] = false;
1022+
}
1023+
}
1024+
}
1025+
columnIndex += span;
1026+
}
1027+
1028+
final var generator =
1029+
new CompositeNestedGeneratedValueGenerator(
1030+
new Component.StandardGenerationContextLocator( rootClass.getEntityName() ),
1031+
(ComponentType) component.getType(),
1032+
generators,
1033+
columnValues,
1034+
columnInclusions,
1035+
generatedOnExecutionColumns
1036+
);
1037+
for ( var plan : generationPlans ) {
1038+
generator.addGeneratedValuePlan( plan );
1039+
}
1040+
return generator;
1041+
}
1042+
1043+
private static Generator propertyGenerator(
1044+
Component component,
1045+
Dialect dialect,
1046+
RootClass rootClass,
1047+
GeneratorSettings defaults,
1048+
Property property,
1049+
List<GenerationPlan> generationPlans,
1050+
int propertyIndex) {
1051+
final var value = property.getValue();
1052+
if ( value instanceof SimpleValue simpleValue ) {
1053+
if ( !simpleValue.getCustomIdGeneratorCreator().isAssigned() ) {
1054+
// skip any 'assigned' generators, they would have been
1055+
// handled by the StandardGenerationContextLocator
1056+
final var propertyGenerator = simpleValue.createGenerator( dialect, rootClass, property, defaults );
1057+
if ( propertyGenerator instanceof BeforeExecutionGenerator beforeExecutionGenerator ) {
1058+
generationPlans.add( new Component.ValueGenerationPlan(
1059+
beforeExecutionGenerator,
1060+
component.getType().isMutable()
1061+
? injector( property, getAttributeDeclarer( rootClass, component ) )
1062+
: null,
1063+
propertyIndex
1064+
) );
1065+
}
1066+
return propertyGenerator;
1067+
}
1068+
else {
1069+
return null;
1070+
}
1071+
}
1072+
else {
1073+
return null;
1074+
}
1075+
}
9061076
}

hibernate-core/src/main/java/org/hibernate/event/internal/AbstractSaveEventListener.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,17 +102,19 @@ protected Object saveWithGeneratedId(
102102
final boolean generatedBeforeExecution = generator.generatedBeforeExecution( entity, source );
103103
final Object generatedId;
104104
if ( generatedOnExecution ) {
105-
// the id gets generated by the database
106-
// and is not yet available
107105
if ( generatedBeforeExecution
108106
&& generator instanceof CompositeNestedGeneratedValueGenerator compositeGenerator ) {
107+
// for a composite id, we might need to
108+
// create the composite id instance early
109109
final Object preGeneratedId = compositeGenerator.generate( source, entity );
110110
if ( preGeneratedId == null ) {
111111
throw new IdentifierGenerationException(
112112
"Null id generated for entity '" + persister.getEntityName() + "'" );
113113
}
114114
persister.setIdentifier( entity, preGeneratedId, source );
115115
}
116+
// the id gets generated by the database and is
117+
// not yet available
116118
generatedId = null;
117119
}
118120
else if ( !generator.generatesOnInsert() ) {

hibernate-core/src/main/java/org/hibernate/generator/values/internal/GeneratedValuesHelper.java

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -246,14 +246,12 @@ private static List<? extends ModelPart> getActualGeneratedModelParts(
246246
if ( timing == EventType.INSERT ) {
247247
if ( !supportsArbitraryValues ) {
248248
final var identifierMapping = persister.getIdentifierMapping();
249-
if ( identifierMapping instanceof CompositeIdentifierMapping compositeIdentifier ) {
250-
final var generator = persister.getGenerator();
251-
if ( generator instanceof CompositeNestedGeneratedValueGenerator compositeGenerator ) {
252-
final boolean[] generatedColumns =
253-
compositeGenerator.getGeneratedOnExecutionColumnInclusions();
254-
if ( generatedColumns != null ) {
255-
return generatedIdentifierModelParts( compositeIdentifier, generatedColumns );
256-
}
249+
if ( identifierMapping instanceof CompositeIdentifierMapping compositeIdentifier
250+
&& persister.getGenerator() instanceof CompositeNestedGeneratedValueGenerator compositeGenerator ) {
251+
final boolean[] generatedColumns =
252+
compositeGenerator.getGeneratedOnExecutionColumnInclusions();
253+
if ( generatedColumns != null ) {
254+
return generatedIdentifierModelParts( compositeIdentifier, generatedColumns );
257255
}
258256
}
259257
return List.of( identifierMapping );

hibernate-core/src/main/java/org/hibernate/id/CompositeNestedGeneratedValueGenerator.java

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -171,25 +171,21 @@ public boolean hasAssignedValues() {
171171

172172
@Override
173173
public Object generate(SharedSessionContractImplementor session, Object object) {
174-
final Object context = resolveGenerationContext( session, object );
175-
final var generatedValues = generatedValues( session, object, context );
174+
final Object context = generationContextLocator.locateGenerationContext( session, object );
175+
final Object result = context != null ? context : instantiateEmptyComposite();
176+
final var generatedValues = generatedValues( session, object, result );
176177
if ( generatedValues != null) {
177-
final var values = componentType.getPropertyValues( context );
178+
final var values = componentType.getPropertyValues( result );
178179
for ( int i = 0; i < generatedValues.size(); i++ ) {
179180
values[generationPlans.get( i ).getPropertyIndex()] = generatedValues.get( i );
180181
}
181-
return componentType.replacePropertyValues( context, values, session );
182+
return componentType.replacePropertyValues( result, values, session );
182183
}
183184
else {
184-
return context;
185+
return result;
185186
}
186187
}
187188

188-
private Object resolveGenerationContext(SharedSessionContractImplementor session, Object object) {
189-
final Object context = generationContextLocator.locateGenerationContext( session, object );
190-
return context != null ? context : instantiateEmptyComposite();
191-
}
192-
193189
private Object instantiateEmptyComposite() {
194190
final var mappingModelPart = componentType.getMappingModelPart();
195191
final var embeddable = mappingModelPart.getEmbeddableTypeDescriptor();
@@ -298,8 +294,7 @@ public boolean writePropertyValue(EventType eventType) {
298294
public InsertGeneratedIdentifierDelegate getGeneratedIdentifierDelegate(EntityPersister persister) {
299295
for ( var generator : generators ) {
300296
if ( generator instanceof PostInsertIdentifierGenerator postInsertIdentifierGenerator ) {
301-
final InsertGeneratedIdentifierDelegate delegate =
302-
postInsertIdentifierGenerator.getGeneratedIdentifierDelegate( persister );
297+
final var delegate = postInsertIdentifierGenerator.getGeneratedIdentifierDelegate( persister );
303298
if ( delegate != null ) {
304299
return delegate;
305300
}

0 commit comments

Comments
 (0)