From 2d17f1836714f2f50fbb0567f89180412e7b2673 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Sat, 7 Jun 2025 16:08:10 +0200 Subject: [PATCH 1/3] [#2154] Composite key with generated value not working properly --- .../id/ReactiveIdentifierGenerator.java | 5 + ...ompositeNestedGeneratedValueGenerator.java | 108 ++++++++++++++++++ .../tuple/entity/ReactiveEntityMetamodel.java | 55 +++++++-- 3 files changed, 158 insertions(+), 10 deletions(-) create mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/ReactiveCompositeNestedGeneratedValueGenerator.java diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/ReactiveIdentifierGenerator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/ReactiveIdentifierGenerator.java index 7121ea5c4..83ae3290a 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/ReactiveIdentifierGenerator.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/ReactiveIdentifierGenerator.java @@ -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; @@ -33,4 +34,8 @@ public interface ReactiveIdentifierGenerator extends Generator { * @param session the reactive session */ CompletionStage generate(ReactiveConnectionSupplier session, Object entity); + + default CompletionStage generate(ReactiveConnectionSupplier session, Object owner, Object currentValue, EventType eventType) { + return generate( session, owner ); + } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/ReactiveCompositeNestedGeneratedValueGenerator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/ReactiveCompositeNestedGeneratedValueGenerator.java new file mode 100644 index 000000000..34a685661 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/ReactiveCompositeNestedGeneratedValueGenerator.java @@ -0,0 +1,108 @@ +/* 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 { + + public ReactiveCompositeNestedGeneratedValueGenerator( + CompositeNestedGeneratedValueGenerator generator, + GeneratorCreationContext creationContext, + RuntimeModelCreationContext runtimeModelCreationContext) { + super( + generator.getGenerationContextLocator(), + generator.getCompositeType(), + reactivePlans( generator, creationContext, runtimeModelCreationContext ) + ); + } + + private static List reactivePlans( + CompositeNestedGeneratedValueGenerator generator, + GeneratorCreationContext creationContext, + RuntimeModelCreationContext runtimeModelCreationContext) { + final List 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 generate(ReactiveConnectionSupplier reactiveConnectionSupplier, Object object) { + SharedSessionContractImplementor session = (SharedSessionContractImplementor) reactiveConnectionSupplier; + final Object context = getGenerationContextLocator().locateGenerationContext( session, object ); + + final List generatedValues = getCompositeType().isMutable() + ? null + : new ArrayList<>( getGenerationPlans().size() ); + return loop( getGenerationPlans(), generationPlan -> { + 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" ); + } + } ).thenCompose( v -> { + 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); + } + } ); + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/tuple/entity/ReactiveEntityMetamodel.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/tuple/entity/ReactiveEntityMetamodel.java index 2e20e7781..f8c5d1dd7 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/tuple/entity/ReactiveEntityMetamodel.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/tuple/entity/ReactiveEntityMetamodel.java @@ -10,6 +10,7 @@ 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; @@ -18,6 +19,7 @@ import org.hibernate.id.enhanced.SequenceStyleGenerator; import org.hibernate.id.enhanced.TableGenerator; import org.hibernate.id.enhanced.TableStructure; +import org.hibernate.mapping.Component; import org.hibernate.mapping.GeneratorCreator; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.SimpleValue; @@ -25,6 +27,7 @@ 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; @@ -67,12 +70,9 @@ 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 SimpleValue identifier = (SimpleValue) persistentClass.getIdentifier(); + setCustomIdGenerator( persistentClass, creationContext, identifier ); + final Generator idgenerator = identifier // returns the cached Generator if it was already created .createGenerator( @@ -86,12 +86,35 @@ private static Generator buildIdGenerator( } } + private static void setCustomIdGenerator( + PersistentClass persistentClass, + RuntimeModelCreationContext creationContext, + SimpleValue identifier) { + final GeneratorCreator customIdGeneratorCreator = identifier.getCustomIdGeneratorCreator(); + if ( identifier instanceof Component component ) { + final Generator componentIdentifierGenerator = component.createGenerator( + creationContext.getDialect(), + persistentClass.getRootClass(), + persistentClass.getIdentifierProperty(), + creationContext.getGeneratorSettings() + ); + identifier.setCustomIdGeneratorCreator( context -> + augmentWithReactiveGenerator( componentIdentifierGenerator, context, creationContext ) + ); + } + else { + identifier.setCustomIdGeneratorCreator( context -> + augmentWithReactiveGenerator( customIdGeneratorCreator.createGenerator( context ), context, creationContext ) + ); + } + } + 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 ); } @@ -100,16 +123,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; } From e15b34c007395240bbc6c506d0b9c2bf59423d59 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Tue, 10 Jun 2025 11:51:34 +0200 Subject: [PATCH 2/3] [#2154] ReactiveEntityMetamodel#buildIdGenerator() refactoring --- .../tuple/entity/ReactiveEntityMetamodel.java | 102 ++++++++++++------ 1 file changed, 71 insertions(+), 31 deletions(-) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/tuple/entity/ReactiveEntityMetamodel.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/tuple/entity/ReactiveEntityMetamodel.java index f8c5d1dd7..14e227358 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/tuple/entity/ReactiveEntityMetamodel.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/tuple/entity/ReactiveEntityMetamodel.java @@ -8,6 +8,8 @@ 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; @@ -19,9 +21,10 @@ import org.hibernate.id.enhanced.SequenceStyleGenerator; import org.hibernate.id.enhanced.TableGenerator; import org.hibernate.id.enhanced.TableStructure; -import org.hibernate.mapping.Component; -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; @@ -32,7 +35,9 @@ 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; @@ -71,44 +76,26 @@ private static Generator buildIdGenerator( } else { final SimpleValue identifier = (SimpleValue) persistentClass.getIdentifier(); - setCustomIdGenerator( persistentClass, creationContext, identifier ); - - final Generator idgenerator = identifier - // returns the cached Generator if it was already created - .createGenerator( + 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; } } - private static void setCustomIdGenerator( - PersistentClass persistentClass, - RuntimeModelCreationContext creationContext, - SimpleValue identifier) { - final GeneratorCreator customIdGeneratorCreator = identifier.getCustomIdGeneratorCreator(); - if ( identifier instanceof Component component ) { - final Generator componentIdentifierGenerator = component.createGenerator( - creationContext.getDialect(), - persistentClass.getRootClass(), - persistentClass.getIdentifierProperty(), - creationContext.getGeneratorSettings() - ); - identifier.setCustomIdGeneratorCreator( context -> - augmentWithReactiveGenerator( componentIdentifierGenerator, context, creationContext ) - ); - } - else { - identifier.setCustomIdGeneratorCreator( context -> - augmentWithReactiveGenerator( customIdGeneratorCreator.createGenerator( context ), context, creationContext ) - ); - } - } - public static Generator augmentWithReactiveGenerator( Generator generator, GeneratorCreationContext creationContext, @@ -156,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(); + } + } } From 20d7bebc81eb4aed53813db042412e423670df26 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Sat, 7 Jun 2025 16:07:10 +0200 Subject: [PATCH 3/3] [#2154] Test Composite Id with generated values --- .../CompositeIdWithGeneratedValuesTest.java | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 hibernate-reactive-core/src/test/java/org/hibernate/reactive/CompositeIdWithGeneratedValuesTest.java diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/CompositeIdWithGeneratedValuesTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/CompositeIdWithGeneratedValuesTest.java new file mode 100644 index 000000000..53fb4b910 --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/CompositeIdWithGeneratedValuesTest.java @@ -0,0 +1,92 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive; + +import org.junit.jupiter.api.Test; + +import io.vertx.junit5.Timeout; +import io.vertx.junit5.VertxTestContext; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.IdClass; +import jakarta.persistence.SequenceGenerator; +import java.util.Collection; +import java.util.List; + +import static java.util.concurrent.TimeUnit.MINUTES; + +@Timeout(value = 10, timeUnit = MINUTES) + +public class CompositeIdWithGeneratedValuesTest extends BaseReactiveTest{ + + @Override + protected Collection> annotatedEntities() { + return List.of( Product.class); + } + + @Test + public void testCompositeIdWithGeneratedValues(VertxTestContext context) { + Product product = new Product("name", 1L); + test( context, openSession() + .thenCompose( session -> session + .persist( product ) + .thenCompose( v -> session.flush() )) + ); + } + + @Entity + @IdClass(ProductId.class) + public static class Product { + @Id + private Long version; + + @Id + @GeneratedValue + @SequenceGenerator(name = "product_seq", sequenceName = "product_seq") + private Long id; + + private String name; + + public Product() { + } + + public Product(String name, Long version) { + this.name = name; + this.version = version; + } + + public Long getVersion() { + return version; + } + + public void setVersion(Long version) { + this.version = version; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + } + + public static class ProductId { + private Long version; + private Long id; + + private ProductId() { + } + + public ProductId(Long version, Long id) { + this.version = version; + this.id = id; + } + } +}