diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/EntityTypes.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/EntityTypes.java index 5aede7c5e..a027c181b 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/EntityTypes.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/EntityTypes.java @@ -10,13 +10,17 @@ import org.hibernate.AssertionFailure; import org.hibernate.HibernateException; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.EntityUniqueKey; import org.hibernate.engine.spi.PersistenceContext; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.proxy.HibernateProxy; +import org.hibernate.proxy.LazyInitializer; import org.hibernate.reactive.persister.entity.impl.ReactiveEntityPersister; import org.hibernate.reactive.session.impl.ReactiveQueryExecutorLookup; import org.hibernate.reactive.session.impl.ReactiveSessionImpl; @@ -26,11 +30,16 @@ import org.hibernate.type.Type; import static org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer.UNFETCHED_PROPERTY; +import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable; +import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptable; import static org.hibernate.property.access.internal.PropertyAccessStrategyBackRefImpl.UNKNOWN; +import static org.hibernate.proxy.HibernateProxy.extractLazyInitializer; import static org.hibernate.reactive.engine.impl.ForeignKeys.getEntityIdentifierIfNotUnsaved; +import static org.hibernate.reactive.session.impl.SessionUtil.checkEntityFound; import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; import static org.hibernate.reactive.util.impl.CompletionStages.loop; import static org.hibernate.reactive.util.impl.CompletionStages.nullFuture; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; /** * Reactive operations that really belong to {@link EntityType} @@ -69,8 +78,11 @@ static boolean isNull(EntityType entityType, Object owner, SharedSessionContract OneToOneType type = (OneToOneType) entityType; String propertyName = type.getPropertyName(); if ( propertyName != null ) { - EntityPersister ownerPersister = session.getFactory().getMetamodel() - .entityPersister( entityType.getAssociatedEntityName() ); + final EntityPersister ownerPersister = session.getFactory() + .getRuntimeMetamodels() + .getMappingMetamodel() + .getEntityDescriptor( entityType.getAssociatedEntityName() ); + Object id = session.getContextEntityIdentifier( owner ); EntityKey entityKey = session.generateEntityKey( id, ownerPersister ); return session.getPersistenceContextInternal().isPropertyNull( entityKey, propertyName ); @@ -124,14 +136,12 @@ static CompletionStage loadByUniqueKey( else { return persister .reactiveLoadByUniqueKey( uniqueKeyPropertyName, key, session ) - .thenApply( loaded -> { - // If the entity was not in the Persistence Context, but was found now, - // add it to the Persistence Context - if ( loaded != null ) { - persistenceContext.addEntity( euk, loaded ); - } - return loaded; - } ); + .thenApply( ukResult -> loadHibernateProxyEntity( ukResult, session ) + .thenApply( targetUK -> { + persistenceContext.addEntity( euk, targetUK ); + return targetUK; + } ) + ); } } @@ -173,7 +183,16 @@ public static CompletionStage replace( session, owner, copyCache - ).thenAccept( copy -> copied[i] = copy ) + ).thenCompose( copy -> { + if ( copy instanceof CompletionStage ) { + return ( (CompletionStage) copy ) + .thenAccept( nonStageCopy -> copied[i] = nonStageCopy ); + } + else { + copied[i] = copy; + return voidFuture(); + } + } ) ).thenApply( v -> copied ); } @@ -186,7 +205,7 @@ public static CompletionStage replace( final Type[] types, final SessionImplementor session, final Object owner, - final Map copyCache, + final Map copyCache, final ForeignKeyDirection foreignKeyDirection) { Object[] copied = new Object[original.length]; for ( int i = 0; i < types.length; i++ ) { @@ -207,17 +226,25 @@ public static CompletionStage replace( } } return loop( 0, types.length, - i -> original[i] != UNFETCHED_PROPERTY && original[i] != UNKNOWN - && types[i] instanceof EntityType, - i -> replace( - (EntityType) types[i], - original[i], - target[i] == UNFETCHED_PROPERTY ? null : target[i], - session, - owner, - copyCache, - foreignKeyDirection - ).thenAccept( copy -> copied[i] = copy ) + i -> original[i] != UNFETCHED_PROPERTY && original[i] != UNKNOWN + && types[i] instanceof EntityType, + i -> replace( + (EntityType) types[i], + original[i], + target[i] == UNFETCHED_PROPERTY ? null : target[i], + session, + owner, + copyCache, + foreignKeyDirection + ).thenCompose( copy -> { + if ( copy instanceof CompletionStage ) { + return ( (CompletionStage) copy ).thenAccept( nonStageCopy -> copied[i] = nonStageCopy ); + } + else { + copied[i] = copy; + return voidFuture(); + } + } ) ).thenApply( v -> copied ); } @@ -230,7 +257,7 @@ private static CompletionStage replace( Object target, SessionImplementor session, Object owner, - Map copyCache, + Map copyCache, ForeignKeyDirection foreignKeyDirection) throws HibernateException { boolean include = entityType.isAssociationType() @@ -250,7 +277,7 @@ private static CompletionStage replace( Object target, SessionImplementor session, Object owner, - Map copyCache) { + Map copyCache) { if ( original == null ) { return nullFuture(); } @@ -296,11 +323,12 @@ private static CompletionStage resolveIdOrUniqueKey( Object original, SessionImplementor session, Object owner, - Map copyCache) { + Map copyCache) { return getIdentifier( entityType, original, session ) .thenCompose( id -> { if ( id == null ) { - throw new AssertionFailure( "non-transient entity has a null id: " + original.getClass().getName() ); + throw new AssertionFailure( "non-transient entity has a null id: " + original.getClass() + .getName() ); } // For the special case of a @ManyToOne joined on a (non-primary) unique key, // the "id" class is actually the associated entity object itself, but treated @@ -311,6 +339,11 @@ private static CompletionStage resolveIdOrUniqueKey( .thenCompose( fetched -> { Object idOrUniqueKey = entityType.getIdentifierOrUniqueKeyType( session.getFactory() ) .replace( fetched, null, session, owner, copyCache ); + if ( idOrUniqueKey instanceof CompletionStage ) { + return ( (CompletionStage) idOrUniqueKey ) + .thenCompose( key -> resolve( entityType, key, owner, session ) ); + } + return resolve( entityType, idOrUniqueKey, owner, session ); } ); } ); @@ -319,7 +352,10 @@ private static CompletionStage resolveIdOrUniqueKey( /** * see EntityType#getIdentifier(Object, SharedSessionContractImplementor) */ - private static CompletionStage getIdentifier(EntityType entityType, Object value, SessionImplementor session) { + private static CompletionStage getIdentifier( + EntityType entityType, + Object value, + SessionImplementor session) { if ( entityType.isReferenceToIdentifierProperty() ) { // tolerates nulls return getEntityIdentifierIfNotUnsaved( entityType.getAssociatedEntityName(), value, session ); @@ -328,17 +364,89 @@ private static CompletionStage getIdentifier(EntityType entityType, Obje return nullFuture(); } - EntityPersister entityPersister = entityType.getAssociatedEntityPersister( session.getFactory() ); + if ( value instanceof HibernateProxy ) { + return getIdentifierFromHibernateProxy( entityType, (HibernateProxy) value, session ); + } + + final LazyInitializer lazyInitializer = extractLazyInitializer( value ); + if ( lazyInitializer != null ) { + /* + If the value is a Proxy and the property access is field, the value returned by + `attributeMapping.getAttributeMetadata().getPropertyAccess().getGetter().get( object )` + is always null except for the id, we need the to use the proxy implementation to + extract the property value. + */ + value = lazyInitializer.getImplementation(); + } + else if ( isPersistentAttributeInterceptable( value ) ) { + /* + If the value is an instance of PersistentAttributeInterceptable, and it is not initialized + we need to force initialization the get the property value + */ + final PersistentAttributeInterceptor interceptor = asPersistentAttributeInterceptable( value ).$$_hibernate_getInterceptor(); + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + ( (EnhancementAsProxyLazinessInterceptor) interceptor ).forceInitialize( value, null ); + } + } + final EntityPersister entityPersister = entityType.getAssociatedEntityPersister( session.getFactory() ); String uniqueKeyPropertyName = entityType.getRHSUniqueKeyPropertyName(); Object propertyValue = entityPersister.getPropertyValue( value, uniqueKeyPropertyName ); // We now have the value of the property-ref we reference. However, // we need to dig a little deeper, as that property might also be // an entity type, in which case we need to resolve its identifier - Type type = entityPersister.getPropertyType( uniqueKeyPropertyName ); + final Type type = entityPersister.getPropertyType( uniqueKeyPropertyName ); if ( type.isEntityType() ) { propertyValue = getIdentifier( (EntityType) type, propertyValue, session ); } return completedFuture( propertyValue ); + + } + + private static CompletionStage getIdentifierFromHibernateProxy( + EntityType entityType, + HibernateProxy proxy, + SharedSessionContractImplementor session) { + LazyInitializer initializer = proxy.getHibernateLazyInitializer(); + final String entityName = initializer.getEntityName(); + final Object identifier = initializer.getIdentifier(); + return ( (ReactiveSessionImpl) session ).reactiveImmediateLoad( entityName, identifier ) + .thenApply( entity -> { + checkEntityFound( session, entityName, identifier, entity ); + initializer.setSession( session ); + initializer.setImplementation( entity ); + if ( entity != null ) { + final EntityPersister entityPersister = entityType.getAssociatedEntityPersister( session.getFactory() ); + String uniqueKeyPropertyName = entityType.getRHSUniqueKeyPropertyName(); + Object propertyValue = entityPersister.getPropertyValue( entity, uniqueKeyPropertyName ); + // We now have the value of the property-ref we reference. However, + // we need to dig a little deeper, as that property might also be + // an entity type, in which case we need to resolve its identifier + final Type type = entityPersister.getPropertyType( uniqueKeyPropertyName ); + if ( type.isEntityType() ) { + propertyValue = getIdentifier( (EntityType) type, propertyValue, (SessionImplementor) session ); + } + return completedFuture( propertyValue ); + } + return nullFuture(); + } ); + } + + private static CompletionStage loadHibernateProxyEntity( + Object entity, + SharedSessionContractImplementor session) { + if ( entity instanceof HibernateProxy ) { + LazyInitializer initializer = ( (HibernateProxy) entity ).getHibernateLazyInitializer(); + final String entityName = initializer.getEntityName(); + final Object identifier = initializer.getIdentifier(); + return ( (ReactiveSessionImpl) session ).reactiveImmediateLoad( entityName, identifier ) + .thenApply( result -> { + checkEntityFound( session, entityName, identifier, result ); + return result; + } ); + } + else { + return completedFuture( entity ); + } } } diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EagerUniqueKeyTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EagerUniqueKeyTest.java index c84a5a7e4..83f51e10d 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EagerUniqueKeyTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EagerUniqueKeyTest.java @@ -5,16 +5,18 @@ */ package org.hibernate.reactive; -import io.vertx.junit5.Timeout; -import io.vertx.junit5.VertxTestContext; +import java.io.Serializable; +import java.util.Collection; +import java.util.List; import org.hibernate.Hibernate; import org.hibernate.annotations.Fetch; import org.hibernate.annotations.FetchMode; -import org.junit.jupiter.api.Disabled; 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; @@ -24,10 +26,6 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; -import java.io.Serializable; -import java.util.Collection; -import java.util.List; - import static java.util.concurrent.TimeUnit.MINUTES; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -73,7 +71,6 @@ public void testMergeDetached(VertxTestContext context) { ) ) ); } - @Disabled // see https://github.com/hibernate/hibernate-reactive/issues/1504 @Test public void testMergeReference(VertxTestContext context) { Bar bar = new Bar( "unique3" ); diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/LazyUniqueKeyTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/LazyUniqueKeyTest.java index 9833a6b36..b9ce6b0a5 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/LazyUniqueKeyTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/LazyUniqueKeyTest.java @@ -13,7 +13,6 @@ import org.hibernate.annotations.FetchMode; import org.hibernate.reactive.testing.DBSelectionExtension; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -53,17 +52,17 @@ protected Collection> annotatedEntities() { public void testFindSelect(VertxTestContext context) { Foo foo = new Foo( new Bar( "unique" ) ); test( context, getSessionFactory() - .withTransaction( session -> session - .persist( foo ) - .thenCompose( v -> session.flush() ) - .thenAccept( v -> session.clear() ) - .thenCompose( v -> session.find( Foo.class, foo.id ) ) + .withTransaction( session -> session + .persist( foo ) + .thenCompose( v -> session.flush() ) + .thenAccept( v -> session.clear() ) + .thenCompose( v -> session.find( Foo.class, foo.id ) ) // .thenApply( result -> { // assertFalse( Hibernate.isInitialized( result.bar) ); // return result; // } ) - .thenCompose( result -> session.fetch( result.bar ) ) - .thenAccept( bar -> assertEquals( "unique", bar.key ) ) ) + .thenCompose( result -> session.fetch( result.bar ) ) + .thenAccept( bar -> assertEquals( "unique", bar.key ) ) ) ); } @@ -77,22 +76,24 @@ public void testMergeDetached(VertxTestContext context) { .thenCompose( result -> getSessionFactory() .withTransaction( session -> session.fetch( result.bar ) .thenAccept( b -> assertEquals( "unique2", b.key ) ) - ) ) ); + ) ) ); } - @Disabled // see https://github.com/hibernate/hibernate-reactive/issues/1504 @Test public void testMergeReference(VertxTestContext context) { Bar bar = new Bar( "unique3" ); test( context, getSessionFactory() .withTransaction( session -> session.persist( bar ) ) .thenCompose( i -> getSessionFactory() - .withTransaction( session-> session.merge( new Foo( session.getReference( Bar.class, bar.id ) ) ) ) + .withTransaction( session -> session.merge( new Foo( session.getReference( + Bar.class, + bar.id + ) ) ) ) ) .thenCompose( result -> getSessionFactory() - .withTransaction( session-> session.fetch( result.bar ) - .thenAccept( b -> assertEquals( "unique3", b.key ) ) - ) ) ); + .withTransaction( session -> session.fetch( result.bar ) + .thenAccept( b -> assertEquals( "unique3", b.key ) ) + ) ) ); } @Test @@ -101,13 +102,13 @@ public void testPersistReference(VertxTestContext context) { test( context, getSessionFactory() .withTransaction( session -> session.persist( bar ) ) .thenCompose( i -> getSessionFactory() - .withTransaction( session-> { + .withTransaction( session -> { Foo foo = new Foo( session.getReference( Bar.class, bar.id ) ); return session.persist( foo ).thenApply( v -> foo ); } ) ) .thenCompose( result -> getSessionFactory() - .withTransaction( session-> session.fetch( result.bar ) + .withTransaction( session -> session.fetch( result.bar ) .thenAccept( b -> assertEquals( "unique3", b.getKey() ) ) ) ) ); }