diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/AbstractReactiveSaveEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/AbstractReactiveSaveEventListener.java index e9d68b00e..c0ef66b17 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/AbstractReactiveSaveEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/AbstractReactiveSaveEventListener.java @@ -144,55 +144,59 @@ else if ( generator instanceof Assigned ) { // the entity instance, so it will be available // to the entity in the @PrePersist callback if ( generator instanceof ReactiveIdentifierGenerator ) { - return ( (ReactiveIdentifierGenerator) generator ) - .generate( ( ReactiveConnectionSupplier ) source, entity ) - .thenApply( id -> castToIdentifierType( id, persister ) ) - .thenCompose( gid -> performSaveWithId( - entity, - context, - source, - persister, - generator, - gid, - requiresImmediateIdAccess, - false - ) ); + return generateId( entity, source, (ReactiveIdentifierGenerator) generator, persister ) + .thenCompose( gid -> { + if ( gid == SHORT_CIRCUIT_INDICATOR ) { + source.getIdentifier( entity ); + return voidFuture(); + } + persister.setIdentifier( entity, gid, source ); + return reactivePerformSave( + entity, + gid, + persister, + generatedOnExecution, + context, + source, + false + ); + } ); } generatedId = ( (BeforeExecutionGenerator) generator ).generate( source, entity, null, INSERT ); + if ( generatedId == SHORT_CIRCUIT_INDICATOR ) { + source.getIdentifier( entity ); + return voidFuture(); + } + persister.setIdentifier( entity, generatedId, source ); } final Object id = castToIdentifierType( generatedId, persister ); - return reactivePerformSave( entity, id, persister, generatedOnExecution, context, source, requiresImmediateIdAccess ); + final boolean delayIdentityInserts = !source.isTransactionInProgress() && !requiresImmediateIdAccess && generatedOnExecution; + return reactivePerformSave( entity, id, persister, generatedOnExecution, context, source, delayIdentityInserts ); } - private CompletionStage performSaveWithId( + private CompletionStage generateId( Object entity, - C context, EventSource source, - EntityPersister persister, - Generator generator, - Object generatedId, - boolean requiresImmediateIdAccess, - boolean generatedOnExecution) { - if ( generatedId == null ) { - throw new IdentifierGenerationException( "null id generated for: " + entity.getClass() ); - } - if ( generatedId == SHORT_CIRCUIT_INDICATOR ) { - source.getIdentifier( entity ); - return voidFuture(); - } - if ( LOG.isDebugEnabled() ) { - LOG.debugf( - "Generated identifier: %s, using strategy: %s", - persister.getIdentifierType().toLoggableString( generatedId, source.getFactory() ), - generator.getClass().getName() - ); - } - final boolean delayIdentityInserts = - !source.isTransactionInProgress() - && !requiresImmediateIdAccess - && generatedOnExecution; - return reactivePerformSave( entity, generatedId, persister, false, context, source, delayIdentityInserts ); + ReactiveIdentifierGenerator generator, + EntityPersister persister) { + return generator + .generate( (ReactiveConnectionSupplier) source, entity ) + .thenApply( id -> { + final Object generatedId = castToIdentifierType( id, persister ); + if ( generatedId == null ) { + throw new IdentifierGenerationException( "null id generated for: " + entity.getClass() ); + } + if ( LOG.isDebugEnabled() ) { + LOG.debugf( + "Generated identifier: %s, using strategy: %s", + persister.getIdentifierType().toLoggableString( generatedId, source.getFactory() ), + generator.getClass().getName() + ); + } + return generatedId; + } + ); } /** @@ -232,10 +236,7 @@ protected CompletionStage reactivePerformSave( if ( persister.getGenerator() instanceof Assigned ) { id = persister.getIdentifier( entity, source ); if ( id == null ) { - throw new IdentifierGenerationException( - "Identifier of entity '" + persister.getEntityName() - + "' must be manually assigned before calling 'persist()'" - ); + return failedFuture( new IdentifierGenerationException( "Identifier of entity '" + persister.getEntityName() + "' must be manually assigned before calling 'persist()'" ) ); } } diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToOneMapsIdAndGeneratedIdTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToOneMapsIdAndGeneratedIdTest.java new file mode 100644 index 000000000..7aca2c611 --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToOneMapsIdAndGeneratedIdTest.java @@ -0,0 +1,154 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive; + +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +import org.junit.jupiter.api.Test; + +import io.vertx.junit5.Timeout; +import io.vertx.junit5.VertxTestContext; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.MapsId; +import jakarta.persistence.OneToOne; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.assertj.core.api.Assertions.assertThat; + +@Timeout(value = 10, timeUnit = MINUTES) +public class OneToOneMapsIdAndGeneratedIdTest extends BaseReactiveTest { + + @Override + protected Collection> annotatedEntities() { + return List.of( Person.class, NaturalPerson.class ); + } + + @Test + public void testPersist(VertxTestContext context) { + NaturalPerson naturalPerson = new NaturalPerson( "natual" ); + Person person = new Person( "person", naturalPerson ); + + test( + context, getMutinySessionFactory() + .withTransaction( session -> session.persist( person ) ) + .chain( () -> getMutinySessionFactory() + .withTransaction( session -> session + .find( Person.class, person.getId() ) + .invoke( result -> { + assertThat( result ).isNotNull(); + assertThat( result.getNaturalPerson() ).isNotNull(); + assertThat( result.getNaturalPerson().getId() ).isEqualTo( result.getId() ); + } ) ) + ) + ); + + } + + @Entity + public static class Person { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String name; + + @OneToOne(mappedBy = "person", cascade = CascadeType.ALL) + private NaturalPerson naturalPerson; + + public Person() { + } + + public Person(String name, NaturalPerson naturalPerson) { + this.name = name; + this.naturalPerson = naturalPerson; + naturalPerson.person = this; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public NaturalPerson getNaturalPerson() { + return naturalPerson; + } + + @Override + public boolean equals(Object o) { + if ( o == null || getClass() != o.getClass() ) { + return false; + } + Person person = (Person) o; + return Objects.equals( name, person.name ); + } + + @Override + public int hashCode() { + return Objects.hashCode( name ); + } + } + + @Entity + public static class NaturalPerson { + + @Id + private Long id; + + @Column + private String name; + + @OneToOne(fetch = FetchType.LAZY) + @MapsId + private Person person; + + public NaturalPerson() { + } + + public NaturalPerson(String name) { + this.name = name; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public Person getPerson() { + return person; + } + + @Override + public boolean equals(Object o) { + if ( o == null || getClass() != o.getClass() ) { + return false; + } + NaturalPerson that = (NaturalPerson) o; + return Objects.equals( name, that.name ); + } + + @Override + public int hashCode() { + return Objects.hashCode( name ); + } + } + + +}