Skip to content
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

Fix issue with merge and getReference #1696

Merged
merged 4 commits into from
Jul 5, 2023
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 @@ -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;
Expand All @@ -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}
Expand Down Expand Up @@ -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 );
Expand Down Expand Up @@ -124,14 +136,12 @@ static CompletionStage<Object> 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;
} )
);

}
}
Expand Down Expand Up @@ -173,7 +183,16 @@ public static CompletionStage<Object[]> 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 );
}

Expand All @@ -186,7 +205,7 @@ public static CompletionStage<Object[]> replace(
final Type[] types,
final SessionImplementor session,
final Object owner,
final Map copyCache,
final Map<Object, Object> copyCache,
final ForeignKeyDirection foreignKeyDirection) {
Object[] copied = new Object[original.length];
for ( int i = 0; i < types.length; i++ ) {
Expand All @@ -207,17 +226,25 @@ public static CompletionStage<Object[]> 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 );
}

Expand All @@ -230,7 +257,7 @@ private static CompletionStage<Object> replace(
Object target,
SessionImplementor session,
Object owner,
Map copyCache,
Map<Object, Object> copyCache,
ForeignKeyDirection foreignKeyDirection)
throws HibernateException {
boolean include = entityType.isAssociationType()
Expand All @@ -250,7 +277,7 @@ private static CompletionStage<Object> replace(
Object target,
SessionImplementor session,
Object owner,
Map copyCache) {
Map<Object, Object> copyCache) {
if ( original == null ) {
return nullFuture();
}
Expand Down Expand Up @@ -296,11 +323,12 @@ private static CompletionStage<Object> resolveIdOrUniqueKey(
Object original,
SessionImplementor session,
Object owner,
Map copyCache) {
Map<Object, Object> 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
Expand All @@ -311,6 +339,11 @@ private static CompletionStage<Object> 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 );
} );
} );
Expand All @@ -319,7 +352,10 @@ private static CompletionStage<Object> resolveIdOrUniqueKey(
/**
* see EntityType#getIdentifier(Object, SharedSessionContractImplementor)
*/
private static CompletionStage<Object> getIdentifier(EntityType entityType, Object value, SessionImplementor session) {
private static CompletionStage<Object> getIdentifier(
EntityType entityType,
Object value,
SessionImplementor session) {
if ( entityType.isReferenceToIdentifierProperty() ) {
// tolerates nulls
return getEntityIdentifierIfNotUnsaved( entityType.getAssociatedEntityName(), value, session );
Expand All @@ -328,17 +364,89 @@ private static CompletionStage<Object> 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 );

Check notice

Code scanning / CodeQL

Deprecated method or constructor invocation

Invoking [EntityPersister.getPropertyType](1) should be avoided because it has been deprecated.
if ( type.isEntityType() ) {
propertyValue = getIdentifier( (EntityType) type, propertyValue, session );
}
return completedFuture( propertyValue );

}

private static CompletionStage<Object> 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 );

Check notice

Code scanning / CodeQL

Deprecated method or constructor invocation

Invoking [EntityPersister.getPropertyType](1) should be avoided because it has been deprecated.
if ( type.isEntityType() ) {
propertyValue = getIdentifier( (EntityType) type, propertyValue, (SessionImplementor) session );
}
return completedFuture( propertyValue );
}
return nullFuture();
} );
}

private static CompletionStage<Object> 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 );
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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" );
Expand Down
Loading