Skip to content

Composite key with generated value not working properly #2303

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package org.hibernate.reactive.id;

import org.hibernate.Incubating;
import org.hibernate.generator.EventType;
import org.hibernate.generator.Generator;
import org.hibernate.id.IdentifierGenerator;
import org.hibernate.reactive.session.ReactiveConnectionSupplier;
Expand Down Expand Up @@ -33,4 +34,8 @@
* @param session the reactive session
*/
CompletionStage<Id> generate(ReactiveConnectionSupplier session, Object entity);

default CompletionStage<Id> generate(ReactiveConnectionSupplier session, Object owner, Object currentValue, EventType eventType) {

Check notice

Code scanning / CodeQL

Useless parameter Note

The parameter 'currentValue' is never used.

Check notice

Code scanning / CodeQL

Useless parameter Note

The parameter 'eventType' is never used.
return generate( session, owner );
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/* Hibernate, Relational Persistence for Idiomatic Java
*
* SPDX-License-Identifier: Apache-2.0
* Copyright: Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.reactive.id.impl;

import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.generator.BeforeExecutionGenerator;
import org.hibernate.generator.Generator;
import org.hibernate.generator.GeneratorCreationContext;
import org.hibernate.id.CompositeNestedGeneratedValueGenerator;
import org.hibernate.id.IdentifierGenerationException;
import org.hibernate.mapping.Component;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
import org.hibernate.reactive.id.ReactiveIdentifierGenerator;
import org.hibernate.reactive.session.ReactiveConnectionSupplier;
import org.hibernate.reactive.tuple.entity.ReactiveEntityMetamodel;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletionStage;

import static org.hibernate.generator.EventType.INSERT;
import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture;
import static org.hibernate.reactive.util.impl.CompletionStages.loop;

public class ReactiveCompositeNestedGeneratedValueGenerator extends CompositeNestedGeneratedValueGenerator implements
ReactiveIdentifierGenerator<Object> {

public ReactiveCompositeNestedGeneratedValueGenerator(
CompositeNestedGeneratedValueGenerator generator,
GeneratorCreationContext creationContext,
RuntimeModelCreationContext runtimeModelCreationContext) {
super(
generator.getGenerationContextLocator(),
generator.getCompositeType(),
reactivePlans( generator, creationContext, runtimeModelCreationContext )
);
}

private static List<GenerationPlan> reactivePlans(
CompositeNestedGeneratedValueGenerator generator,
GeneratorCreationContext creationContext,
RuntimeModelCreationContext runtimeModelCreationContext) {
final List<GenerationPlan> plans = new ArrayList<>();
for ( GenerationPlan plan : generator.getGenerationPlans() ) {
final GenerationPlan reactivePlane = new Component.ValueGenerationPlan(
(BeforeExecutionGenerator) ReactiveEntityMetamodel.augmentWithReactiveGenerator(
plan.getGenerator(),
creationContext,
runtimeModelCreationContext
),
plan.getInjector(),
plan.getPropertyIndex()
);
plans.add( reactivePlane );
}
return plans;
}

@Override
public CompletionStage<Object> generate(ReactiveConnectionSupplier reactiveConnectionSupplier, Object object) {
SharedSessionContractImplementor session = (SharedSessionContractImplementor) reactiveConnectionSupplier;
final Object context = getGenerationContextLocator().locateGenerationContext( session, object );

final List<Object> generatedValues = getCompositeType().isMutable()
? null
: new ArrayList<>( getGenerationPlans().size() );
return loop( getGenerationPlans(), generationPlan -> generateIdentifier(
reactiveConnectionSupplier,
object,
generationPlan,
session,
generatedValues,
context
) )
.thenCompose( v -> handleGeneratedValues( generatedValues, context, session ) );
}

private CompletionStage<?> generateIdentifier(
ReactiveConnectionSupplier reactiveConnectionSupplier,
Object object,
GenerationPlan generationPlan,
SharedSessionContractImplementor session,
List<Object> generatedValues,
Object context) {
final Generator generator = generationPlan.getGenerator();
if ( generator.generatedBeforeExecution( object, session ) ) {
if ( generator instanceof ReactiveIdentifierGenerator<?> reactiveIdentifierGenerator ) {
return reactiveIdentifierGenerator
.generate( reactiveConnectionSupplier, object )
.thenAccept( generated -> {
if ( generatedValues != null ) {
generatedValues.add( generated );
}
else {
generationPlan.getInjector().set( context, generated );
}
} );
}
else {
final Object currentValue = generator.allowAssignedIdentifiers()
? getCompositeType().getPropertyValue( context, generationPlan.getPropertyIndex(), session )
: null;
return completedFuture( ( (BeforeExecutionGenerator) generator )
.generate( session, object, currentValue, INSERT ) );
}
}
else {
throw new IdentifierGenerationException( "Identity generation isn't supported for composite ids" );
}
}

private CompletionStage<Object> handleGeneratedValues(
List<Object> generatedValues,
Object context,
SharedSessionContractImplementor session) {
if ( generatedValues != null ) {
final Object[] values = getCompositeType().getPropertyValues( context );
for ( int i = 0; i < generatedValues.size(); i++ ) {
values[getGenerationPlans().get( i ).getPropertyIndex()] = generatedValues.get( i );
}
return completedFuture( getCompositeType().replacePropertyValues( context, values, session ) );
}
else {
return completedFuture( context );
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@
import java.util.function.Function;


import org.hibernate.boot.model.relational.Database;
import org.hibernate.boot.model.relational.SqlStringGenerationContext;
import org.hibernate.generator.Generator;
import org.hibernate.generator.GeneratorCreationContext;
import org.hibernate.id.CompositeNestedGeneratedValueGenerator;
import org.hibernate.id.Configurable;
import org.hibernate.id.IdentifierGenerator;
import org.hibernate.id.SelectGenerator;
Expand All @@ -18,18 +21,23 @@
import org.hibernate.id.enhanced.SequenceStyleGenerator;
import org.hibernate.id.enhanced.TableGenerator;
import org.hibernate.id.enhanced.TableStructure;
import org.hibernate.mapping.GeneratorCreator;
import org.hibernate.mapping.GeneratorSettings;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.RootClass;
import org.hibernate.mapping.SimpleValue;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.reactive.id.ReactiveIdentifierGenerator;
import org.hibernate.reactive.id.impl.EmulatedSequenceReactiveIdentifierGenerator;
import org.hibernate.reactive.id.impl.ReactiveCompositeNestedGeneratedValueGenerator;
import org.hibernate.reactive.id.impl.ReactiveGeneratorWrapper;
import org.hibernate.reactive.id.impl.ReactiveSequenceIdentifierGenerator;
import org.hibernate.reactive.id.impl.TableReactiveIdentifierGenerator;
import org.hibernate.reactive.logging.impl.Log;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.tuple.entity.EntityMetamodel;
import org.hibernate.type.Type;

import static java.lang.invoke.MethodHandles.lookup;
import static org.hibernate.reactive.logging.impl.LoggerFactory.make;
Expand Down Expand Up @@ -67,20 +75,22 @@ private static Generator buildIdGenerator(
return existing;
}
else {
SimpleValue identifier = (SimpleValue) persistentClass.getIdentifier();
GeneratorCreator customIdGeneratorCreator = identifier.getCustomIdGeneratorCreator();
identifier.setCustomIdGeneratorCreator( context -> {
Generator generator = customIdGeneratorCreator.createGenerator( context );
return augmentWithReactiveGenerator( generator, context, creationContext );
} );
final Generator idgenerator = identifier
// returns the cached Generator if it was already created
.createGenerator(
final SimpleValue identifier = (SimpleValue) persistentClass.getIdentifier();
final Generator idgenerator = augmentWithReactiveGenerator(
identifier.createGenerator(
creationContext.getDialect(),
persistentClass.getRootClass(),
persistentClass.getIdentifierProperty(),
creationContext.getGeneratorSettings()
);
),
new IdGeneratorCreationContext(
persistentClass.getRootClass(),
persistentClass.getIdentifierProperty(),
creationContext.getGeneratorSettings(),
identifier,
creationContext
),
creationContext );
creationContext.getGenerators().put( rootName, idgenerator );
return idgenerator;
}
Expand All @@ -90,8 +100,8 @@ public static Generator augmentWithReactiveGenerator(
Generator generator,
GeneratorCreationContext creationContext,
RuntimeModelCreationContext runtimeModelCreationContext) {
if ( generator instanceof SequenceStyleGenerator ) {
final DatabaseStructure structure = ( (SequenceStyleGenerator) generator ).getDatabaseStructure();
if ( generator instanceof SequenceStyleGenerator sequenceStyleGenerator) {
final DatabaseStructure structure = sequenceStyleGenerator.getDatabaseStructure();
if ( structure instanceof TableStructure ) {
return initialize( (IdentifierGenerator) generator, new EmulatedSequenceReactiveIdentifierGenerator( (TableStructure) structure, runtimeModelCreationContext ), creationContext );
}
Expand All @@ -100,16 +110,28 @@ public static Generator augmentWithReactiveGenerator(
}
throw LOG.unknownStructureType();
}
if ( generator instanceof TableGenerator ) {
if ( generator instanceof TableGenerator tableGenerator ) {
return initialize(
(IdentifierGenerator) generator,
new TableReactiveIdentifierGenerator( (TableGenerator) generator, runtimeModelCreationContext ),
new TableReactiveIdentifierGenerator( tableGenerator, runtimeModelCreationContext ),
creationContext
);
}
if ( generator instanceof SelectGenerator ) {
throw LOG.selectGeneratorIsNotSupportedInHibernateReactive();
}
if ( generator instanceof CompositeNestedGeneratedValueGenerator compositeNestedGeneratedValueGenerator ) {
final ReactiveCompositeNestedGeneratedValueGenerator reactiveCompositeNestedGeneratedValueGenerator = new ReactiveCompositeNestedGeneratedValueGenerator(
compositeNestedGeneratedValueGenerator,
creationContext,
runtimeModelCreationContext
);
return initialize(
(IdentifierGenerator) generator,
reactiveCompositeNestedGeneratedValueGenerator,
creationContext
);
}
//nothing to do
return generator;
}
Expand All @@ -121,4 +143,57 @@ private static Generator initialize(
( (Configurable) reactiveIdGenerator ).initialize( creationContext.getSqlStringGenerationContext() );
return new ReactiveGeneratorWrapper( reactiveIdGenerator, idGenerator );
}

private record IdGeneratorCreationContext(
RootClass rootClass,
Property property,
GeneratorSettings defaults,
SimpleValue identifier,
RuntimeModelCreationContext buildingContext) implements GeneratorCreationContext {

@Override
public Database getDatabase() {
return buildingContext.getBootModel().getDatabase();
}

@Override
public ServiceRegistry getServiceRegistry() {
return buildingContext.getBootstrapContext().getServiceRegistry();
}

@Override
public SqlStringGenerationContext getSqlStringGenerationContext() {
return defaults.getSqlStringGenerationContext();
}

@Override
public String getDefaultCatalog() {
return defaults.getDefaultCatalog();
}

@Override
public String getDefaultSchema() {
return defaults.getDefaultSchema();
}

@Override
public RootClass getRootClass() {
return rootClass;
}

@Override
public PersistentClass getPersistentClass() {
return rootClass;
}

@Override
public Property getProperty() {
return property;
}

@Override
public Type getType() {
return identifier.getType();
}
}
}
Loading
Loading