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 83ae3290a..ffb0867e3 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,11 +6,14 @@ package org.hibernate.reactive.id; import org.hibernate.Incubating; +import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.generator.EventType; -import org.hibernate.generator.Generator; import org.hibernate.id.IdentifierGenerator; +import org.hibernate.reactive.logging.impl.Log; +import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.session.ReactiveConnectionSupplier; +import java.lang.invoke.MethodHandles; import java.util.concurrent.CompletionStage; /** @@ -26,7 +29,7 @@ * @see IdentifierGenerator */ @Incubating -public interface ReactiveIdentifierGenerator extends Generator { +public interface ReactiveIdentifierGenerator extends IdentifierGenerator { /** * Returns a generated identifier, via a {@link CompletionStage}. @@ -38,4 +41,18 @@ public interface ReactiveIdentifierGenerator extends Generator { default CompletionStage generate(ReactiveConnectionSupplier session, Object owner, Object currentValue, EventType eventType) { return generate( session, owner ); } + + @Override + default Id generate( + SharedSessionContractImplementor session, + Object owner, + Object currentValue, + EventType eventType){ + throw LoggerFactory.make( Log.class, MethodHandles.lookup() ).nonReactiveMethodCall( "generate(ReactiveConnectionSupplier, Object, Object, EventType)" ); + } + + @Override + default Object generate(SharedSessionContractImplementor session, Object object){ + throw LoggerFactory.make( Log.class, MethodHandles.lookup() ).nonReactiveMethodCall( "generate(ReactiveConnectionSupplier, Object)" ); + } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/ReactiveOnExecutionGenerator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/ReactiveOnExecutionGenerator.java new file mode 100644 index 000000000..2fea352b1 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/ReactiveOnExecutionGenerator.java @@ -0,0 +1,24 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.id; + +import org.hibernate.dialect.Dialect; +import org.hibernate.generator.OnExecutionGenerator; +import org.hibernate.id.insert.InsertGeneratedIdentifierDelegate; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.reactive.id.insert.ReactiveInsertReturningDelegate; + +public interface ReactiveOnExecutionGenerator extends OnExecutionGenerator { + + @Override + default InsertGeneratedIdentifierDelegate getGeneratedIdentifierDelegate(EntityPersister persister) { + Dialect dialect = persister.getFactory().getJdbcServices().getDialect(); + // Hibernate ORM allows the selection of different strategies based on the property `hibernate.jdbc.use_get_generated_keys`. + // But that's a specific JDBC property and with Vert.x we only have one viable option for each supported database. + return new ReactiveInsertReturningDelegate( persister, dialect ); + } + +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java index d67cc2018..f49cf2110 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java @@ -70,6 +70,7 @@ import org.hibernate.reactive.query.sql.spi.ReactiveNativeQueryImplementor; import org.hibernate.reactive.query.sqm.internal.ReactiveQuerySqmImpl; import org.hibernate.reactive.query.sqm.internal.ReactiveSqmSelectionQueryImpl; +import org.hibernate.reactive.session.ReactiveConnectionSupplier; import org.hibernate.reactive.session.ReactiveSqmQueryImplementor; import org.hibernate.reactive.session.ReactiveStatelessSession; import org.hibernate.reactive.util.impl.CompletionStages.Completable; @@ -438,7 +439,7 @@ private CompletionStage generatedIdBeforeInsert( private CompletionStage generateIdForInsert(Object entity, Generator generator, ReactiveEntityPersister persister) { if ( generator instanceof ReactiveIdentifierGenerator reactiveGenerator ) { - return reactiveGenerator.generate( this, this ) + return reactiveGenerator.generate( (ReactiveConnectionSupplier) this, this ) .thenApply( id -> castToIdentifierType( id, persister ) ); } diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/BeforeExecutionIdGeneratorTypeTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/BeforeExecutionIdGeneratorTypeTest.java new file mode 100644 index 000000000..c7d1f4fab --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/BeforeExecutionIdGeneratorTypeTest.java @@ -0,0 +1,107 @@ +/* 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.hibernate.annotations.IdGeneratorType; +import org.hibernate.generator.EventType; +import org.hibernate.reactive.id.ReactiveIdentifierGenerator; +import org.hibernate.reactive.session.ReactiveConnectionSupplier; +import org.hibernate.reactive.util.impl.CompletionStages; + +import org.junit.jupiter.api.Test; + +import io.vertx.junit5.Timeout; +import io.vertx.junit5.VertxTestContext; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.Collection; +import java.util.EnumSet; +import java.util.List; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.atomic.AtomicLong; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +@Timeout(value = 10, timeUnit = MINUTES) +public class BeforeExecutionIdGeneratorTypeTest extends BaseReactiveTest { + + @Override + protected Collection> annotatedEntities() { + return List.of( Person.class ); + } + + @Test + public void testPersist(VertxTestContext context) { + Person person = new Person(); + test( + context, openSession() + .thenCompose( session -> session.persist( person ) ) + .thenAccept( v -> { + assertThat( person.getId() ).isNotNull(); + } ) + ); + } + + @Entity(name = "Person") + public static class Person { + @Id + @SimpleId + long id; + + String name; + + public Person() { + } + + public Person(String name) { + this.name = name; + } + + public long getId() { + return id; + } + + public String getName() { + return name; + } + } + + @Target(ElementType.FIELD) + @Retention(RetentionPolicy.RUNTIME) + @IdGeneratorType(SimpleGenerator.class) + public @interface SimpleId { + } + + public static class SimpleGenerator implements ReactiveIdentifierGenerator { + + private AtomicLong sequence = new AtomicLong( 1 ); + + public SimpleGenerator() { + } + + @Override + public boolean generatedOnExecution() { + return false; + } + + @Override + public EnumSet getEventTypes() { + return EnumSet.of( EventType.INSERT ); + } + + + @Override + public CompletionStage generate(ReactiveConnectionSupplier session, Object entity) { + return CompletionStages.completedFuture( sequence.getAndIncrement() ); + } + } + +} diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OnExecutionGeneratorTypeTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OnExecutionGeneratorTypeTest.java new file mode 100644 index 000000000..8b8990903 --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OnExecutionGeneratorTypeTest.java @@ -0,0 +1,119 @@ +/* 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.hibernate.annotations.IdGeneratorType; +import org.hibernate.annotations.ValueGenerationType; +import org.hibernate.dialect.Dialect; +import org.hibernate.generator.EventType; +import org.hibernate.generator.EventTypeSets; +import org.hibernate.reactive.id.ReactiveOnExecutionGenerator; + +import org.junit.jupiter.api.Test; + +import io.vertx.junit5.Timeout; +import io.vertx.junit5.VertxTestContext; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.Collection; +import java.util.Date; +import java.util.EnumSet; +import java.util.List; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +@Timeout(value = 10, timeUnit = MINUTES) +public class OnExecutionGeneratorTypeTest extends BaseReactiveTest { + + @Override + protected Collection> annotatedEntities() { + return List.of( Person.class ); + } + + @Test + public void testPersist(VertxTestContext context) { + Person person = new Person( "Davide" ); + test( context, getSessionFactory() + .withTransaction( session -> session.persist( person ) ) + .thenAccept( v -> { + assertThat( person.getId() ).isNotNull(); + assertThat( person.getCreated() ).isNotNull(); + } ) + ); + } + + @Entity(name = "Person") + public static class Person { + @Id + @FunctionCreatedValueId + Date id; + + String name; + + @FunctionCreatedValue + Date created; + + public Person() { + } + + public Person(String name) { + this.name = name; + } + + public Date getId() { + return id; + } + + public String getName() { + return name; + } + + public Date getCreated() { + return created; + } + } + + @Target(ElementType.FIELD) + @Retention(RetentionPolicy.RUNTIME) + @IdGeneratorType(FunctionCreationValueGeneration.class) + public @interface FunctionCreatedValueId { + } + + @Target(ElementType.FIELD) + @Retention(RetentionPolicy.RUNTIME) + @ValueGenerationType(generatedBy = FunctionCreationValueGeneration.class) + public @interface FunctionCreatedValue {} + + public static class FunctionCreationValueGeneration + implements ReactiveOnExecutionGenerator { + + @Override + public boolean referenceColumnsInSql(Dialect dialect) { + return true; + } + + @Override + public boolean writePropertyValue() { + return false; + } + + @Override + public String[] getReferencedColumnValues(Dialect dialect) { + return new String[] { dialect.currentTimestamp() }; + } + + @Override + public EnumSet getEventTypes() { + return EventTypeSets.INSERT_ONLY; + } + } + +}