diff --git a/README.md b/README.md index 31dba7666..62ab172f5 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Learn more at . Hibernate Reactive has been tested with: -- Java 11, 17, 20, 21 +- Java 11, 17, 20, 21, 22 - PostgreSQL 16 - MySQL 8 - MariaDB 11 @@ -37,7 +37,7 @@ Hibernate Reactive has been tested with: - CockroachDB v24 - MS SQL Server 2022 - Oracle 23 -- [Hibernate ORM][] 6.5.2.Final +- [Hibernate ORM][] 6.6.0.CR2 - [Vert.x Reactive PostgreSQL Client](https://vertx.io/docs/vertx-pg-client/java/) 4.5.9 - [Vert.x Reactive MySQL Client](https://vertx.io/docs/vertx-mysql-client/java/) 4.5.9 - [Vert.x Reactive Db2 Client](https://vertx.io/docs/vertx-db2-client/java/) 4.5.9 diff --git a/build.gradle b/build.gradle index 8b37fa1eb..815345bc4 100644 --- a/build.gradle +++ b/build.gradle @@ -53,7 +53,7 @@ version = projectVersion // ./gradlew clean build -PhibernateOrmVersion=5.6.15-SNAPSHOT ext { if ( !project.hasProperty('hibernateOrmVersion') ) { - hibernateOrmVersion = '6.5.2.Final' + hibernateOrmVersion = '6.6.0.CR2' } if ( !project.hasProperty( 'hibernateOrmGradlePluginVersion' ) ) { // Same as ORM as default diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/ReactiveActionQueue.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/ReactiveActionQueue.java index 144978be0..034c9f583 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/ReactiveActionQueue.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/ReactiveActionQueue.java @@ -241,16 +241,8 @@ public CompletionStage addAction(ReactiveEntityInsertAction action) { return addInsertAction( action ); } - private CompletionStage addInsertAction( ReactiveEntityInsertAction insert) { - CompletionStage ret = voidFuture(); - if ( insert.isEarlyInsert() ) { - // For early inserts, must execute inserts before finding non-nullable transient entities. - // TODO: find out why this is necessary - LOG.tracev( "Executing inserts before finding non-nullable transient entities for early insert: [{0}]", insert ); - ret = ret.thenCompose( v -> executeInserts() ); - } - - return ret + private CompletionStage addInsertAction(ReactiveEntityInsertAction insert) { + return executeEarlyInsertsIfRequired( insert ) .thenCompose( v -> insert.reactiveFindNonNullableTransientEntities() ) .thenCompose( nonNullables -> { if ( nonNullables == null ) { @@ -270,40 +262,51 @@ private CompletionStage addInsertAction( ReactiveEntityInsertAction insert } ); } + private CompletionStage executeEarlyInsertsIfRequired(ReactiveEntityInsertAction insert) { + if ( insert.isEarlyInsert() ) { + // For early inserts, must execute inserts before finding non-nullable transient entities. + // TODO: find out why this is necessary + LOG.tracev( + "Executing inserts before finding non-nullable transient entities for early insert: [{0}]", + insert + ); + return executeInserts(); + } + return voidFuture(); + } + private CompletionStage addResolvedEntityInsertAction(ReactiveEntityInsertAction insert) { - CompletionStage ret; if ( insert.isEarlyInsert() ) { - LOG.trace( "Executing insertions before resolved early-insert" ); - ret = executeInserts() - .thenCompose( v -> { + // For early inserts, must execute inserts before finding non-nullable transient entities. + LOG.tracev( "Executing inserts before finding non-nullable transient entities for early insert: [{0}]", insert ); + return executeInserts().thenCompose( v -> { LOG.debug( "Executing identity-insert immediately" ); return execute( insert ); - } ); + } ) + .thenCompose( v -> postResolvedEntityInsertAction( insert ) ); } else { LOG.trace( "Adding resolved non-early insert action." ); OrderedActions.EntityInsertAction.ensureInitialized( this ); this.insertions.add( new ReactiveEntityInsertActionHolder( insert ) ); - ret = voidFuture(); + return postResolvedEntityInsertAction( insert ); } + } - return ret.thenCompose( v -> { - if ( !insert.isVeto() ) { - CompletionStage comp = insert.reactiveMakeEntityManaged(); - if ( unresolvedInsertions == null ) { - return comp; - } - else { - return comp.thenCompose( vv -> loop( + private CompletionStage postResolvedEntityInsertAction(ReactiveEntityInsertAction insert) { + if ( !insert.isVeto() ) { + return insert.reactiveMakeEntityManaged().thenCompose( v -> { + if ( unresolvedInsertions != null ) { + return loop( unresolvedInsertions.resolveDependentActions( insert.getInstance(), session.getSharedContract() ), resolvedAction -> addResolvedEntityInsertAction( (ReactiveEntityRegularInsertAction) resolvedAction ) - ) ); + ); } - } - else { - throw new ReactiveEntityActionVetoException( "The ReactiveEntityInsertAction was vetoed.", insert ); - } - } ); + return voidFuture(); + } ); + } + + throw new ReactiveEntityActionVetoException( "The ReactiveEntityInsertAction was vetoed.", insert ); } private static String[] convertTimestampSpaces(Serializable[] spaces) { diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/Cascade.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/Cascade.java index bb0666ebe..36466b556 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/Cascade.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/Cascade.java @@ -5,7 +5,6 @@ */ package org.hibernate.reactive.engine.impl; -import java.lang.invoke.MethodHandles; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; @@ -23,6 +22,7 @@ import org.hibernate.engine.spi.CollectionEntry; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.PersistenceContext; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.Status; import org.hibernate.event.spi.DeleteContext; import org.hibernate.event.spi.EventSource; @@ -30,17 +30,21 @@ import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.reactive.logging.impl.Log; -import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.session.ReactiveSession; import org.hibernate.type.AssociationType; import org.hibernate.type.CollectionType; import org.hibernate.type.ComponentType; import org.hibernate.type.CompositeType; import org.hibernate.type.EntityType; +import org.hibernate.type.ManyToOneType; +import org.hibernate.type.OneToOneType; import org.hibernate.type.Type; +import static java.lang.invoke.MethodHandles.lookup; +import static java.util.Collections.EMPTY_LIST; import static org.hibernate.engine.internal.ManagedTypeHelper.isHibernateProxy; import static org.hibernate.pretty.MessageHelper.infoString; +import static org.hibernate.reactive.logging.impl.LoggerFactory.make; import static org.hibernate.reactive.util.impl.CompletionStages.loop; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; import static org.hibernate.type.ForeignKeyDirection.TO_PARENT; @@ -54,35 +58,11 @@ * @author Gavin King * @see CascadingAction */ -public final class Cascade { +public final class Cascade { - private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); + private static final Log LOG = make( Log.class, lookup() ); - private final CascadingAction action; - private final EntityPersister persister; - private final Object parent; - private final EventSource eventSource; - private final C context; - private CascadePoint cascadePoint; - - private CompletionStage stage = voidFuture(); - - /** - * @param persister The parent's entity persister - * @param parent The parent reference. - */ - public Cascade(final CascadingAction action, - final CascadePoint cascadePoint, - final EntityPersister persister, - final Object parent, - final C context, - final EventSource eventSource) { - this.action = action; - this.parent = parent; - this.persister = persister; - this.cascadePoint = cascadePoint; - this.eventSource = eventSource; - this.context = context; + private Cascade() { } public static CompletionStage fetchLazyAssociationsBeforeCascade( @@ -93,13 +73,16 @@ public static CompletionStage fetchLazyAssociationsBeforeCascade( CompletionStage beforeDelete = voidFuture(); if ( persister.hasCascades() ) { - CascadeStyle[] cascadeStyles = persister.getPropertyCascadeStyles(); - Object[] state = persister.getValues( entity ); + final CascadeStyle[] cascadeStyles = persister.getPropertyCascadeStyles(); + final Object[] state = persister.getValues( entity ); for (int i = 0; i < cascadeStyles.length; i++) { if ( cascadeStyles[i].doCascade( action.delegate() ) ) { Object fetchable = state[i]; if ( !Hibernate.isInitialized( fetchable ) ) { - beforeDelete = beforeDelete.thenCompose( v -> session.unwrap(ReactiveSession.class).reactiveFetch( fetchable, true ) ); + beforeDelete = beforeDelete.thenCompose( v -> session + .unwrap( ReactiveSession.class ) + .reactiveFetch( fetchable, true ) + ); } } } @@ -107,165 +90,220 @@ public static CompletionStage fetchLazyAssociationsBeforeCascade( return beforeDelete; } - /** - * Cascade an action from the parent entity instance to all its children. - */ - public CompletionStage cascade() throws HibernateException { - return voidFuture().thenCompose( v -> { - CacheMode cacheMode = eventSource.getCacheMode(); - if ( action == CascadingActions.DELETE ) { - eventSource.setCacheMode( CacheMode.GET ); - } - eventSource.getPersistenceContextInternal().incrementCascadeLevel(); - return cascadeInternal().whenComplete( (vv, e) -> { - eventSource.getPersistenceContextInternal().decrementCascadeLevel(); - eventSource.setCacheMode( cacheMode ); - } ); - } ); + public static CompletionStage cascade( + final CascadingAction action, + final CascadePoint cascadePoint, + final EventSource eventSource, + final EntityPersister persister, + final Object parent, + final T anything) throws HibernateException { + CacheMode cacheMode = eventSource.getCacheMode(); + // In Hibernate actually set the cache before calling cascade for the remove, but this solution reduces some code + if ( action == CascadingActions.REMOVE ) { + eventSource.setCacheMode( CacheMode.GET ); + } + // Hibernate ORM actually increment/decrement the level before calling cascade, but keeping it here avoid extra + // code every time we need to cascade + eventSource.getPersistenceContextInternal().incrementCascadeLevel(); + return voidFuture() + .thenCompose( v -> cascadeInternal( action, cascadePoint, eventSource, persister, parent, anything ) ) + .whenComplete( (unused, throwable) -> { + eventSource.getPersistenceContextInternal().decrementCascadeLevel(); + eventSource.setCacheMode( cacheMode ); + } ); } - private CompletionStage cascadeInternal() throws HibernateException { - if ( persister.hasCascades() || action.requiresNoCascadeChecking() ) { // performance opt + private static CompletionStage cascadeInternal( + CascadingAction action, + CascadePoint cascadePoint, + EventSource eventSource, + EntityPersister persister, + Object parent, + T anything) { + if ( persister.hasCascades() || action == CascadingActions.CHECK_ON_FLUSH ) { // performance opt final boolean traceEnabled = LOG.isTraceEnabled(); if ( traceEnabled ) { LOG.tracev( "Processing cascade {0} for: {1}", action, persister.getEntityName() ); } - final PersistenceContext persistenceContext = eventSource.getPersistenceContextInternal(); - final EntityEntry entry = persistenceContext.getEntry( parent ); - if ( entry != null && entry.getLoadedState() == null && entry.getStatus() == Status.MANAGED && persister.getBytecodeEnhancementMetadata() - .isEnhancedForLazyLoading() ) { - return voidFuture(); - } - - final Type[] types = persister.getPropertyTypes(); - final String[] propertyNames = persister.getPropertyNames(); - final CascadeStyle[] cascadeStyles = persister.getPropertyCascadeStyles(); - final boolean hasUninitializedLazyProperties = persister.hasUninitializedLazyProperties( parent ); - - for ( int i = 0; i < types.length; i++) { - final CascadeStyle style = cascadeStyles[ i ]; - final String propertyName = propertyNames[ i ]; - final boolean isUninitializedProperty = - hasUninitializedLazyProperties && - !persister.getBytecodeEnhancementMetadata().isAttributeLoaded( parent, propertyName ); - - final Type type = types[i]; - if ( style.doCascade( action.delegate() ) ) { - final Object child; - if ( isUninitializedProperty ) { - // parent is a bytecode enhanced entity. - // Cascade to an uninitialized, lazy value only if - // parent is managed in the PersistenceContext. - // If parent is a detached entity being merged, - // then parent will not be in the PersistenceContext - // (so lazy attributes must not be initialized). - if ( entry == null ) { - // parent was not in the PersistenceContext - continue; - } - if ( type.isCollectionType() ) { - // CollectionType#getCollection gets the PersistentCollection - // that corresponds to the uninitialized collection from the - // PersistenceContext. If not present, an uninitialized - // PersistentCollection will be added to the PersistenceContext. - // The action may initialize it later, if necessary. - // This needs to be done even when action.performOnLazyProperty() returns false. - final CollectionType collectionType = (CollectionType) type; - child = collectionType.getCollection( - collectionType.getKeyOfOwner( parent, eventSource ), - eventSource, - parent, - null - ); - } - else if ( type.isComponentType() ) { - // Hibernate does not support lazy embeddables, so this shouldn't happen. - throw new UnsupportedOperationException( - "Lazy components are not supported." - ); + return doCascade( action, cascadePoint, eventSource, persister, parent, anything ) + .thenRun( () -> { + if ( traceEnabled ) { + LOG.tracev( "Done processing cascade {0} for: {1}", action, persister.getEntityName() ); } - else if ( action.performOnLazyProperty() && type.isEntityType() ) { - // Only need to initialize a lazy entity attribute when action.performOnLazyProperty() - // returns true. - LazyAttributeLoadingInterceptor interceptor = persister.getBytecodeEnhancementMetadata() - .extractInterceptor( parent ); - child = interceptor.fetchAttribute( parent, propertyName ); + } ); + } + return voidFuture(); + } - } - else { - // Nothing to do, so just skip cascading to this lazy attribute. - continue; - } + /** + * Cascade an action from the parent entity instance to all its children. + */ + private static CompletionStage doCascade( + final CascadingAction action, + final CascadePoint cascadePoint, + final EventSource eventSource, + final EntityPersister persister, + final Object parent, + final T anything) throws HibernateException { + final PersistenceContext persistenceContext = eventSource.getPersistenceContextInternal(); + final EntityEntry entry = persistenceContext.getEntry( parent ); + + if ( entry != null + && entry.getLoadedState() == null + && entry.getStatus() == Status.MANAGED + && persister.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading() ) { + return voidFuture(); + } + + final Type[] types = persister.getPropertyTypes(); + final String[] propertyNames = persister.getPropertyNames(); + final CascadeStyle[] cascadeStyles = persister.getPropertyCascadeStyles(); + final boolean hasUninitializedLazyProperties = persister.hasUninitializedLazyProperties( parent ); + + CompletionStage stage = voidFuture(); + for ( int i = 0; i < types.length; i++ ) { + final CascadeStyle style = cascadeStyles[i]; + final String propertyName = propertyNames[i]; + final boolean isUninitializedProperty = hasUninitializedLazyProperties + && !persister.getBytecodeEnhancementMetadata().isAttributeLoaded( parent, propertyName ); + + final Type type = types[i]; + if ( style.doCascade( action.delegate() ) ) { + if ( isUninitializedProperty ) { + // parent is a bytecode enhanced entity. + // Cascade to an uninitialized, lazy value only if + // parent is managed in the PersistenceContext. + // If parent is a detached entity being merged, + // then parent will not be in the PersistenceContext + // (so lazy attributes must not be initialized). + if ( entry == null ) { + // parent was not in the PersistenceContext + continue; + } + if ( type.isCollectionType() ) { + // CollectionType#getCollection gets the PersistentCollection + // that corresponds to the uninitialized collection from the + // PersistenceContext. If not present, an uninitialized + // PersistentCollection will be added to the PersistenceContext. + // The action may initialize it later, if necessary. + // This needs to be done even when action.performOnLazyProperty() returns false. + final CollectionType collectionType = (CollectionType) type; + Object child = collectionType.getCollection( + collectionType.getKeyOfOwner( parent, eventSource ), + eventSource, + parent, + null + ); + stage = stage.thenCompose( v -> cascadeProperty( + action, + cascadePoint, + eventSource, + null, + parent, + child, + type, + style, + propertyName, + anything, + false + ) ); + } + else if ( type.isComponentType() ) { + // Hibernate does not support lazy embeddables, so this shouldn't happen. + throw new UnsupportedOperationException( "Lazy components are not supported." ); + } + else if ( action.performOnLazyProperty() && type.isEntityType() ) { + // Only need to initialize a lazy entity attribute when action.performOnLazyProperty() + // returns true. + LazyAttributeLoadingInterceptor interceptor = persister.getBytecodeEnhancementMetadata() + .extractInterceptor( parent ); + stage = stage + .thenCompose( v -> (CompletionStage) interceptor + .fetchAttribute( parent, propertyName ) + ) + .thenCompose( actualChild -> cascadeProperty( + action, + cascadePoint, + eventSource, + null, + parent, + actualChild, + type, + style, + propertyName, + anything, + false + ) ); } else { - child = persister.getValue( parent, i ); + // Nothing to do, so just skip cascading to this lazy attribute. + continue; } - cascadeProperty( + } + else { + Object child = persister.getValue( parent, i ); + stage = stage.thenCompose( v -> cascadeProperty( + action, + cascadePoint, + eventSource, null, + parent, child, type, style, propertyName, + anything, false - ); - } - else { - if ( action.requiresNoCascadeChecking() ) { - noCascade( eventSource, parent, persister, types, i ); - } - // If the property is uninitialized, then there cannot be any orphans. - if ( action.deleteOrphans() && !isUninitializedProperty ) { - cascadeLogicalOneToOneOrphanRemoval( - null, - persister.getValue( parent, i ), - type, - style, - propertyName, - false - ); - } + ) ); } } - - if ( traceEnabled ) { - LOG.tracev( "Done processing cascade {0} for: {1}", action, persister.getEntityName() ); + else { + // If the property is uninitialized, then there cannot be any orphans. + if ( action.deleteOrphans() && !isUninitializedProperty ) { + final int index = i; + stage = stage.thenCompose( v -> cascadeLogicalOneToOneOrphanRemoval( + action, + eventSource, + null, + parent, + persister.getValue( parent, index ), + type, + style, + propertyName, + false + ) ); + } } } - return stage; } - private void noCascade( - final EventSource eventSource, - final Object parent, - final EntityPersister persister, - final Type[] types, - final int i) { - stage = stage.thenCompose( v -> action.noCascade( eventSource, parent, persister, types[i], i ) ); - } - /** * Cascade an action to the child or children */ - private void cascadeProperty( + private static CompletionStage cascadeProperty( + final CascadingAction action, + final CascadePoint cascadePoint, + final EventSource eventSource, List componentPath, + final Object parent, final Object child, final Type type, final CascadeStyle style, final String propertyName, + final T anything, final boolean isCascadeDeleteEnabled) throws HibernateException { if ( child != null ) { if ( type.isAssociationType() ) { final AssociationType associationType = (AssociationType) type; - if ( cascadeAssociationNow( cascadePoint, associationType ) ) { - cascadeAssociation( - componentPath, - child, - type, - style, - isCascadeDeleteEnabled - ); + final boolean unownedTransient = eventSource.getSessionFactory() + .getSessionFactoryOptions() + .isUnownedAssociationTransientCheck(); + if ( cascadeAssociationNow( action, cascadePoint, associationType, eventSource.getFactory(), unownedTransient ) ) { + final List path = componentPath; + return cascadeAssociation( action, cascadePoint, eventSource, path, parent, child, type, style, anything, isCascadeDeleteEnabled ) + .thenCompose( v -> cascadeLogicalOneToOne( action, eventSource, path, parent, child, type, style, propertyName, isCascadeDeleteEnabled ) ); } } else if ( type.isComponentType() ) { @@ -275,25 +313,39 @@ else if ( type.isComponentType() ) { if ( componentPath != null ) { componentPath.add( propertyName ); } - cascadeComponent( - componentPath, - child, - (CompositeType) type - ); + final List path = componentPath; + return cascadeComponent( action, cascadePoint, eventSource, path, parent, child, (CompositeType) type, anything ) + .thenRun( () -> { + if ( path != null ) { + path.remove( path.size() - 1 ); + } + } ) + .thenCompose( v -> cascadeLogicalOneToOne( action, eventSource, path, parent, child, type, style, propertyName, isCascadeDeleteEnabled ) ); } } + return cascadeLogicalOneToOne( action, eventSource, componentPath, parent, child, type, style, propertyName, isCascadeDeleteEnabled ); + } - cascadeLogicalOneToOneOrphanRemoval( - componentPath, - child, - type, - style, - propertyName, - isCascadeDeleteEnabled ); + private static CompletionStage cascadeLogicalOneToOne( + CascadingAction action, + EventSource eventSource, + List componentPath, + Object parent, + Object child, + Type type, + CascadeStyle style, + String propertyName, + boolean isCascadeDeleteEnabled) { + return isLogicalOneToOne( type ) + ? cascadeLogicalOneToOneOrphanRemoval( action, eventSource, componentPath, parent, child, type, style, propertyName, isCascadeDeleteEnabled ) + : voidFuture(); } - private void cascadeLogicalOneToOneOrphanRemoval( + private static CompletionStage cascadeLogicalOneToOneOrphanRemoval( + final CascadingAction action, + final EventSource eventSource, final List componentPath, + final Object parent, final Object child, final Type type, final CascadeStyle style, @@ -355,7 +407,7 @@ private void cascadeLogicalOneToOneOrphanRemoval( // associated one-to-one. if ( child == loadedValue ) { // do nothing - return; + return voidFuture(); } } @@ -369,24 +421,24 @@ private void cascadeLogicalOneToOneOrphanRemoval( ); } - final Object loaded = loadedValue; if ( type.isAssociationType() - && ( (AssociationType) type ).getForeignKeyDirection().equals(TO_PARENT) ) { + && ( (AssociationType) type ).getForeignKeyDirection().equals( TO_PARENT ) ) { // If FK direction is to-parent, we must remove the orphan *before* the queued update(s) // occur. Otherwise, replacing the association on a managed entity, without manually // nulling and flushing, causes FK constraint violations. - stage = stage.thenCompose( v -> ( (ReactiveSession) eventSource ) - .reactiveRemoveOrphanBeforeUpdates( entityName, loaded ) ); + return ( (ReactiveSession) eventSource ) + .reactiveRemoveOrphanBeforeUpdates( entityName, loadedValue ); } else { // Else, we must delete after the updates. - stage = stage.thenCompose( v -> ( (ReactiveSession) eventSource ) - .reactiveRemove( entityName, loaded, isCascadeDeleteEnabled, DeleteContext.create() ) ); + return ( (ReactiveSession) eventSource ) + .reactiveRemove( entityName, loadedValue, isCascadeDeleteEnabled, DeleteContext.create() ); } } } } } + return voidFuture(); } /** @@ -397,22 +449,55 @@ private void cascadeLogicalOneToOneOrphanRemoval( * * @return True if the attribute represents a logical one to one association */ - private boolean isLogicalOneToOne(Type type) { + private static boolean isLogicalOneToOne(Type type) { return type.isEntityType() && ( (EntityType) type ).isLogicalOneToOne(); } - private boolean cascadeAssociationNow(final CascadePoint cascadePoint, AssociationType associationType) { - return associationType.getForeignKeyDirection().cascadeNow( cascadePoint ); + private static boolean cascadeAssociationNow( + CascadingAction action, + CascadePoint cascadePoint, + AssociationType associationType, + SessionFactoryImplementor factory, + boolean unownedTransient) { + return associationType.getForeignKeyDirection().cascadeNow( cascadePoint ) + // For check on flush, we should only check unowned associations when strictness is enforced + && ( action != CascadingActions.CHECK_ON_FLUSH || unownedTransient || !isUnownedAssociation( associationType, factory ) ); } - private void cascadeComponent( - List componentPath, + private static boolean isUnownedAssociation(AssociationType associationType, SessionFactoryImplementor factory) { + if ( associationType.isEntityType() ) { + if ( associationType instanceof ManyToOneType ) { + final ManyToOneType manyToOne = (ManyToOneType) associationType; + // logical one-to-one + non-null unique key property name indicates unowned + return manyToOne.isLogicalOneToOne() && manyToOne.getRHSUniqueKeyPropertyName() != null; + } + else if ( associationType instanceof OneToOneType ) { + final OneToOneType oneToOne = (OneToOneType) associationType; + // constrained false + non-null unique key property name indicates unowned + return oneToOne.isNullable() && oneToOne.getRHSUniqueKeyPropertyName() != null; + } + } + else if ( associationType.isCollectionType() ) { + // for collections, we can ask the persister if we're on the inverse side + return ( (CollectionType) associationType ).isInverse( factory ); + } + return false; + } + + private static CompletionStage cascadeComponent( + final CascadingAction action, + final CascadePoint cascadePoint, + final EventSource eventSource, + final List componentPath, + final Object parent, final Object child, - final CompositeType componentType) { + final CompositeType componentType, + final T anything) { Object[] children = null; final Type[] types = componentType.getSubtypes(); final String[] propertyNames = componentType.getPropertyNames(); + CompletionStage stage = voidFuture(); for ( int i = 0; i < types.length; i++ ) { final CascadeStyle componentPropertyStyle = componentType.getCascadeStyle( i ); final String subPropertyName = propertyNames[i]; @@ -422,77 +507,106 @@ private void cascadeComponent( // Get children on demand. children = componentType.getPropertyValues( child, eventSource ); } - cascadeProperty( + final Object propertyChild = children[i]; + final Type propertyType = types[i]; + stage = stage.thenCompose( v -> cascadeProperty( + action, + cascadePoint, + eventSource, componentPath, - children[i], - types[i], + parent, + propertyChild, + propertyType, componentPropertyStyle, subPropertyName, + anything, false - ); + ) + ); } } + return stage; } - private void cascadeAssociation( - List componentPath, + private static CompletionStage cascadeAssociation( + final CascadingAction action, + final CascadePoint cascadePoint, + final EventSource eventSource, + final List componentPath, + final Object parent, final Object child, final Type type, final CascadeStyle style, + final T anything, final boolean isCascadeDeleteEnabled) { if ( type.isEntityType() || type.isAnyType() ) { - cascadeToOne( child, type, style, isCascadeDeleteEnabled ); + return cascadeToOne( action, eventSource, parent, child, type, style, anything, isCascadeDeleteEnabled ); } else if ( type.isCollectionType() ) { - cascadeCollection( + return cascadeCollection( + action, + cascadePoint, + eventSource, componentPath, + parent, child, style, + anything, (CollectionType) type ); } + return voidFuture(); } /** * Cascade an action to a collection */ - private void cascadeCollection( - List componentPath, + private static CompletionStage cascadeCollection( + final CascadingAction action, + final CascadePoint cascadePoint, + final EventSource eventSource, + final List componentPath, + final Object parent, final Object child, final CascadeStyle style, + final T anything, final CollectionType type) { - final CollectionPersister persister = - eventSource.getFactory().getMappingMetamodel() - .getCollectionDescriptor( type.getRole() ); + final CollectionPersister persister = eventSource.getFactory().getMappingMetamodel() + .getCollectionDescriptor( type.getRole() ); final Type elemType = persister.getElementType(); - - CascadePoint elementsCascadePoint = cascadePoint; - if ( cascadePoint == CascadePoint.AFTER_INSERT_BEFORE_DELETE ) { - cascadePoint = CascadePoint.AFTER_INSERT_BEFORE_DELETE_VIA_COLLECTION; - } - //cascade to current collection elements if ( elemType.isEntityType() || elemType.isAnyType() || elemType.isComponentType() ) { - cascadeCollectionElements( - componentPath, - child, - type, - style, - elemType, - persister.isCascadeDeleteEnabled() + return cascadeCollectionElements( + action, + cascadePoint == CascadePoint.AFTER_INSERT_BEFORE_DELETE + ? CascadePoint.AFTER_INSERT_BEFORE_DELETE_VIA_COLLECTION + : cascadePoint, + eventSource, + componentPath, + parent, + child, + type, + style, + elemType, + anything, + persister.isCascadeDeleteEnabled() + ); } - - cascadePoint = elementsCascadePoint; + return voidFuture(); } /** * Cascade an action to a to-one association or any type */ - private void cascadeToOne( + private static CompletionStage cascadeToOne( + final CascadingAction action, + final EventSource eventSource, + final Object parent, final Object child, final Type type, final CascadeStyle style, + final T anything, final boolean isCascadeDeleteEnabled) { final String entityName = type.isEntityType() ? ( (EntityType) type ).getAssociatedEntityName() @@ -501,47 +615,66 @@ private void cascadeToOne( //not really necessary, but good for consistency... final PersistenceContext persistenceContext = eventSource.getPersistenceContextInternal(); persistenceContext.addChildParent( child, parent ); - stage = stage.thenCompose( v -> action.cascade( eventSource, child, entityName, context, isCascadeDeleteEnabled ) ) - .whenComplete( (vv, e) -> persistenceContext.removeChildParent( child ) ); + return voidFuture() + .thenCompose( v -> action.cascade( eventSource, child, entityName, anything, isCascadeDeleteEnabled ) ) + .whenComplete( (v, e) -> persistenceContext.removeChildParent( child ) ); } + return voidFuture(); } /** * Cascade to the collection elements */ - private void cascadeCollectionElements( - List componentPath, + private static CompletionStage cascadeCollectionElements( + final CascadingAction action, + final CascadePoint cascadePoint, + final EventSource eventSource, + final List componentPath, + final Object parent, final Object child, final CollectionType collectionType, final CascadeStyle style, final Type elemType, + final T anything, final boolean isCascadeDeleteEnabled) throws HibernateException { - final boolean reallyDoCascade = style.reallyDoCascade( action.delegate() ) - && child != CollectionType.UNFETCHED_COLLECTION; - + final boolean reallyDoCascade = style.reallyDoCascade( action.delegate() ) && child != CollectionType.UNFETCHED_COLLECTION; if ( reallyDoCascade ) { final boolean traceEnabled = LOG.isTraceEnabled(); - if ( traceEnabled ) { - LOG.tracev( "Cascade {0} for collection: {1}", action, collectionType.getRole() ); - } - - final Iterator itr = action.getCascadableChildrenIterator( eventSource, collectionType, child ); - while ( itr.hasNext() ) { - cascadeProperty( - componentPath, - itr.next(), - elemType, - style, - null, - isCascadeDeleteEnabled - ); - } if ( traceEnabled ) { LOG.tracev( "Done cascade {0} for collection: {1}", action, collectionType.getRole() ); } + + final Iterator itr = action.getCascadableChildrenIterator( eventSource, collectionType, child ); + return loop( itr, (value, integer) -> cascadeProperty( + action, + cascadePoint, + eventSource, + componentPath, + parent, + value, + elemType, + style, + collectionType.getRole().substring( collectionType.getRole().lastIndexOf( '.' ) + 1 ), + anything, + isCascadeDeleteEnabled + ) ).thenRun( () -> { + if ( traceEnabled ) { + LOG.tracev( "Done cascade {0} for collection: {1}", action, collectionType.getRole() ); + } + } ).thenCompose( v -> doDeleteOrphans( action, eventSource, child, collectionType, style, elemType ) ); } + return doDeleteOrphans( action, eventSource, child, collectionType, style, elemType ); + } + + private static CompletionStage doDeleteOrphans( + CascadingAction action, + EventSource eventSource, + Object child, + CollectionType collectionType, + CascadeStyle style, + Type elemType) { final boolean deleteOrphans = style.hasOrphanDelete() && action.deleteOrphans() && elemType.isEntityType() @@ -558,39 +691,33 @@ private void cascadeCollectionElements( // 1. newly instantiated collections // 2. arrays (we can't track orphans for detached arrays) final String entityName = collectionType.getAssociatedEntityName( eventSource.getFactory() ); - deleteOrphans( entityName, (PersistentCollection) child ); - - if ( traceEnabled ) { - LOG.tracev( "Done deleting orphans for collection: {0}", collectionType.getRole() ); - } + return doDeleteOrphans( eventSource, entityName, (PersistentCollection) child ) + .thenRun( () -> { + if ( traceEnabled ) { + LOG.tracev( "Done deleting orphans for collection: {0}", collectionType.getRole() ); + } + } ); } + return voidFuture(); } /** * Delete any entities that were removed from the collection */ - private void deleteOrphans(String entityName, PersistentCollection pc) throws HibernateException { - //TODO: suck this logic into the collection! - final Collection orphans; + private static CompletionStage doDeleteOrphans(EventSource eventSource, String entityName, PersistentCollection pc) { + final Collection orphans = getOrphans( eventSource, entityName, pc ); + final ReactiveSession session = (ReactiveSession) eventSource; + return loop( orphans, Objects::nonNull, orphan -> { + LOG.tracev( "Deleting orphaned entity instance: {0}", entityName ); + return session.reactiveRemove( entityName, orphan, false, DeleteContext.create() ); + } ); + } + + private static Collection getOrphans(EventSource eventSource, String entityName, PersistentCollection pc) { if ( pc.wasInitialized() ) { final CollectionEntry ce = eventSource.getPersistenceContextInternal().getCollectionEntry( pc ); - if ( ce == null ) { - return; - } - orphans = ce.getOrphans( entityName, pc ); - } - else { - orphans = pc.getQueuedOrphans( entityName ); + return ce == null ? EMPTY_LIST : ce.getOrphans( entityName, pc ); } - - ReactiveSession session = (ReactiveSession) eventSource; - stage = stage.thenCompose( v -> loop( - orphans, - Objects::nonNull, - orphan -> { - LOG.tracev( "Deleting orphaned entity instance: {0}", entityName ); - return session.reactiveRemove( entityName, orphan, false, DeleteContext.create() ); - } - ) ); + return pc.getQueuedOrphans( entityName ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CascadingAction.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CascadingAction.java index 7c6bdd749..a8707cc11 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CascadingAction.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CascadingAction.java @@ -5,6 +5,9 @@ */ package org.hibernate.reactive.engine.impl; +import java.util.Iterator; +import java.util.concurrent.CompletionStage; + import org.hibernate.HibernateException; import org.hibernate.event.spi.EventSource; import org.hibernate.persister.entity.EntityPersister; @@ -12,9 +15,6 @@ import org.hibernate.type.CollectionType; import org.hibernate.type.Type; -import java.util.Iterator; -import java.util.concurrent.CompletionStage; - /** * A {@link Stage.Session reactive session} operation that may * be cascaded from a parent entity to its children. A non-blocking counterpart @@ -63,13 +63,17 @@ Iterator getCascadableChildrenIterator( */ boolean deleteOrphans(); - /** * Does the specified cascading action require verification of no cascade validity? * * @return True if this action requires no-cascade verification; false otherwise. + * + * @deprecated No longer used */ - boolean requiresNoCascadeChecking(); + @Deprecated(since = "2.4", forRemoval = true) + default boolean requiresNoCascadeChecking() { + return false; + } /** * Called (in the case of {@link #requiresNoCascadeChecking} returning true) to validate @@ -81,6 +85,7 @@ Iterator getCascadableChildrenIterator( * @param propertyType The property type * @param propertyIndex The index of the property within the owner. */ + @Deprecated(since = "2.4", forRemoval = true) CompletionStage noCascade(EventSource session, Object parent, EntityPersister persister, Type propertyType, int propertyIndex); /** diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CascadingActions.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CascadingActions.java index d194fe312..28289c022 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CascadingActions.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CascadingActions.java @@ -10,10 +10,11 @@ import java.util.concurrent.CompletionStage; import org.hibernate.HibernateException; +import org.hibernate.Internal; import org.hibernate.LockOptions; +import org.hibernate.TransientObjectException; import org.hibernate.TransientPropertyValueException; import org.hibernate.engine.spi.EntityEntry; - import org.hibernate.event.spi.DeleteContext; import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.MergeContext; @@ -29,6 +30,9 @@ import org.hibernate.type.Type; import static org.hibernate.engine.internal.ManagedTypeHelper.isHibernateProxy; +import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; +import static org.hibernate.reactive.util.impl.CompletionStages.failedFuture; +import static org.hibernate.reactive.util.impl.CompletionStages.falseFuture; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; /** @@ -48,7 +52,7 @@ private CascadingActions() { /** * @see org.hibernate.Session#remove(Object) */ - public static final CascadingAction DELETE = new BaseCascadingAction<>( org.hibernate.engine.spi.CascadingActions.DELETE ) { + public static final CascadingAction REMOVE = new BaseCascadingAction<>( org.hibernate.engine.spi.CascadingActions.REMOVE ) { @Override public CompletionStage cascade( EventSource session, @@ -150,6 +154,58 @@ public CompletionStage noCascade( } }; + @Internal + public static final CascadingAction CHECK_ON_FLUSH = new BaseCascadingAction<>( org.hibernate.engine.spi.CascadingActions.CHECK_ON_FLUSH ) { + @Override + public CompletionStage cascade( + EventSource session, + Object child, + String entityName, + Void context, + boolean isCascadeDeleteEnabled) throws HibernateException { + if ( child != null ) { + return isChildTransient( session, child, entityName ).thenCompose( isTransient -> { + if ( isTransient ) { + return failedFuture( new TransientObjectException( + "persistent instance references an unsaved transient instance of '" + entityName + "' (save the transient instance before flushing)" ) ); + } + return voidFuture(); + } ); + } + return voidFuture(); + } + }; + + private static CompletionStage isChildTransient(EventSource session, Object child, String entityName) { + if ( isHibernateProxy( child ) ) { + // a proxy is always non-transient + // and ForeignKeys.isTransient() + // is not written to expect a proxy + // TODO: but the proxied entity might have been deleted! + return falseFuture(); + } + else { + final EntityEntry entry = session.getPersistenceContextInternal().getEntry( child ); + if ( entry != null ) { + // if it's associated with the session + // we are good, even if it's not yet + // inserted, since ordering problems + // are detected and handled elsewhere + boolean deleted = entry.getStatus().isDeletedOrGone(); + return completedFuture( deleted ); + } + else { + // TODO: check if it is a merged entity which has not yet been flushed + // Currently this throws if you directly reference a new transient + // instance after a call to merge() that results in its managed copy + // being scheduled for insertion, if the insert has not yet occurred. + // This is not terrible: it's more correct to "swap" the reference to + // point to the managed instance, but it's probably too heavy-handed. + return ForeignKeys.isTransient( entityName, child, null, session ); + } + } + } + /** * @see org.hibernate.Session#merge(Object) */ @@ -173,35 +229,35 @@ public CompletionStage cascade( * @see org.hibernate.Session#refresh(Object) */ public static final CascadingAction REFRESH = new BaseCascadingAction<>( org.hibernate.engine.spi.CascadingActions.REFRESH ) { - @Override - public CompletionStage cascade( - EventSource session, - Object child, - String entityName, - RefreshContext context, - boolean isCascadeDeleteEnabled) - throws HibernateException { - LOG.tracev( "Cascading to refresh: {0}", entityName ); - return session.unwrap( ReactiveSession.class ).reactiveRefresh( child, context ); - } - }; + @Override + public CompletionStage cascade( + EventSource session, + Object child, + String entityName, + RefreshContext context, + boolean isCascadeDeleteEnabled) + throws HibernateException { + LOG.tracev( "Cascading to refresh: {0}", entityName ); + return session.unwrap( ReactiveSession.class ).reactiveRefresh( child, context ); + } + }; /** * @see org.hibernate.Session#lock(Object, org.hibernate.LockMode) */ public static final CascadingAction LOCK = new BaseCascadingAction<>( org.hibernate.engine.spi.CascadingActions.LOCK ) { - @Override - public CompletionStage cascade( - EventSource session, - Object child, - String entityName, - LockOptions context, - boolean isCascadeDeleteEnabled) - throws HibernateException { - LOG.tracev( "Cascading to lock: {0}", entityName ); - return session.unwrap( ReactiveSession.class ).reactiveLock( child, context ); - } - }; + @Override + public CompletionStage cascade( + EventSource session, + Object child, + String entityName, + LockOptions context, + boolean isCascadeDeleteEnabled) + throws HibernateException { + LOG.tracev( "Cascading to lock: {0}", entityName ); + return session.unwrap( ReactiveSession.class ).reactiveLock( child, context ); + } + }; public abstract static class BaseCascadingAction implements CascadingAction { private final org.hibernate.engine.spi.CascadingAction delegate; @@ -225,11 +281,6 @@ public org.hibernate.engine.spi.CascadingAction delegate() { return delegate; } - @Override - public boolean requiresNoCascadeChecking() { - return delegate.requiresNoCascadeChecking(); - } - /** * @see BaseCascadingAction#noCascade(EventSource, Object, EntityPersister, Type, int) */ diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveCollectionRecreateAction.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveCollectionRecreateAction.java index 5f2842183..008755086 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveCollectionRecreateAction.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveCollectionRecreateAction.java @@ -31,22 +31,22 @@ public ReactiveCollectionRecreateAction(final PersistentCollection collection, f public CompletionStage reactiveExecute() { // this method is called when a new non-null collection is persisted // or when an existing (non-null) collection is moved to a new owner - final PersistentCollection collection = getCollection(); preRecreate(); + return ( (ReactiveCollectionPersister) getPersister() ) + .reactiveRecreate( getCollection(), getKey(), getSession() ) + .thenRun( this::afterRecreate ); + } + + private void afterRecreate() { + final PersistentCollection collection = getCollection(); final SharedSessionContractImplementor session = getSession(); - final ReactiveCollectionPersister persister = (ReactiveCollectionPersister) getPersister(); - return persister - .reactiveRecreate( collection, getKey(), session ) - .thenAccept( v -> { - // FIXME: I think we could move everything in a method reference call - session.getPersistenceContextInternal().getCollectionEntry( collection ).afterAction( collection ); - evict(); - postRecreate(); - final StatisticsImplementor statistics = session.getFactory().getStatistics(); - if ( statistics.isStatisticsEnabled() ) { - statistics.recreateCollection( getPersister().getRole() ); - } - } ); + session.getPersistenceContextInternal().getCollectionEntry( collection ).afterAction( collection ); + evict(); + postRecreate(); + final StatisticsImplementor statistics = session.getFactory().getStatistics(); + if ( statistics.isStatisticsEnabled() ) { + statistics.recreateCollection( getPersister().getRole() ); + } } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveCollectionUpdateAction.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveCollectionUpdateAction.java index 21129a1e5..eae872d56 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveCollectionUpdateAction.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveCollectionUpdateAction.java @@ -26,6 +26,7 @@ import org.hibernate.stat.spi.StatisticsImplementor; import static org.hibernate.pretty.MessageHelper.collectionInfoString; +import static org.hibernate.reactive.util.impl.CompletionStages.failedFuture; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; @@ -55,51 +56,59 @@ public CompletionStage reactiveExecute() { final SharedSessionContractImplementor session = getSession(); final ReactiveCollectionPersister reactivePersister = (ReactiveCollectionPersister) getPersister(); final CollectionPersister persister = getPersister(); - final PersistentCollection collection = getCollection(); + final PersistentCollection collection = getCollection(); final boolean affectedByFilters = persister.isAffectedByEnabledFilters( session ); preUpdate(); - final CompletionStage updateStage; + return createUpdateStage( collection, affectedByFilters, reactivePersister, key, session, persister ) + .thenRun( () -> { + session.getPersistenceContextInternal().getCollectionEntry( collection ).afterAction( collection ); + evict(); + postUpdate(); + + final StatisticsImplementor statistics = session.getFactory().getStatistics(); + if ( statistics.isStatisticsEnabled() ) { + statistics.updateCollection( persister.getRole() ); + } + } ); + } + + private CompletionStage createUpdateStage( + PersistentCollection collection, + boolean affectedByFilters, + ReactiveCollectionPersister reactivePersister, + Object key, + SharedSessionContractImplementor session, + CollectionPersister persister) { if ( !collection.wasInitialized() ) { // If there were queued operations, they would have been processed // and cleared by now. // The collection should still be dirty. if ( !collection.isDirty() ) { - throw new AssertionFailure( "collection is not dirty" ); + return failedFuture( new AssertionFailure( "collection is not dirty" ) ); } //do nothing - we only need to notify the cache... - updateStage = voidFuture(); + return voidFuture(); } else if ( !affectedByFilters && collection.empty() ) { - updateStage = emptySnapshot ? voidFuture() : reactivePersister.reactiveRemove( key, session ); + return emptySnapshot ? voidFuture() : reactivePersister.reactiveRemove( key, session ); } else if ( collection.needsRecreate( persister ) ) { if ( affectedByFilters ) { - throw LOG.cannotRecreateCollectionWhileFilterIsEnabled( collectionInfoString( persister, collection, key, session ) ); + return failedFuture( LOG.cannotRecreateCollectionWhileFilterIsEnabled( collectionInfoString( persister, collection, key, session ) ) ); } - updateStage = emptySnapshot + return emptySnapshot ? reactivePersister.reactiveRecreate( collection, key, session ) : reactivePersister.reactiveRemove( key, session ) .thenCompose( v -> reactivePersister.reactiveRecreate( collection, key, session ) ); } else { - updateStage = voidFuture() - .thenCompose( v -> reactivePersister.reactiveDeleteRows( collection, key, session ) ) + return reactivePersister + .reactiveDeleteRows( collection, key, session ) .thenCompose( v -> reactivePersister.reactiveUpdateRows( collection, key, session ) ) .thenCompose( v -> reactivePersister.reactiveInsertRows( collection, key, session ) ); } - - return updateStage.thenAccept( v -> { - session.getPersistenceContextInternal().getCollectionEntry( collection ).afterAction( collection ); - evict(); - postUpdate(); - - final StatisticsImplementor statistics = session.getFactory().getStatistics(); - if ( statistics.isStatisticsEnabled() ) { - statistics.updateCollection( persister.getRole() ); - } - } ); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityIdentityInsertAction.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityIdentityInsertAction.java index 69168c3b1..3dc3a0dc1 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityIdentityInsertAction.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityIdentityInsertAction.java @@ -118,6 +118,11 @@ public EntityKey getEntityKey() { return super.getEntityKey(); } + @Override + public void addCollectionsByKeyToPersistenceContext(PersistenceContext persistenceContext, Object[] objects) { + super.addCollectionsByKeyToPersistenceContext( persistenceContext, objects ); + } + @Override public AbstractEntityInsertAction asAbstractEntityInsertAction() { return this; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityInsertAction.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityInsertAction.java index ef48ba006..4838e6971 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityInsertAction.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityInsertAction.java @@ -11,9 +11,11 @@ import org.hibernate.action.internal.AbstractEntityInsertAction; import org.hibernate.engine.internal.NonNullableTransientDependencies; import org.hibernate.engine.internal.Nullability; -import org.hibernate.engine.internal.Versioning; import org.hibernate.engine.spi.ComparableExecutable; +import org.hibernate.engine.spi.EntityEntry; +import org.hibernate.engine.spi.EntityHolder; import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.Status; @@ -21,6 +23,7 @@ import org.hibernate.reactive.engine.ReactiveActionQueue; import org.hibernate.reactive.engine.ReactiveExecutable; +import static org.hibernate.engine.internal.Versioning.getVersion; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; /** @@ -37,6 +40,7 @@ public interface ReactiveEntityInsertAction extends ReactiveExecutable, Comparab Object getInstance(); String getEntityName(); Object[] getState(); + Object getRowId(); EntityPersister getPersister(); boolean isExecuted(); @@ -62,7 +66,7 @@ default CompletionStage reactiveNullifyTransientReferencesIfNotAlready() { if ( !areTransientReferencesNullified() ) { return new ForeignKeys.Nullifier( getInstance(), false, isEarlyInsert(), (SessionImplementor) getSession(), getPersister() ) .nullifyTransientReferences( getState() ) - .thenAccept( v-> { + .thenAccept( v -> { new Nullability( getSession() ).checkNullability( getState(), getPersister(), false ); setTransientReferencesNullified(); } ); @@ -79,27 +83,35 @@ default CompletionStage reactiveNullifyTransientReferencesIfNotAlready() { */ default CompletionStage reactiveMakeEntityManaged() { return reactiveNullifyTransientReferencesIfNotAlready() - .thenAccept( v -> getSession().getPersistenceContextInternal().addEntity( - getInstance(), - getPersister().isMutable() ? Status.MANAGED : Status.READ_ONLY, - getState(), - getEntityKey(), - Versioning.getVersion( getState(), getPersister() ), - LockMode.WRITE, - isExecuted(), - getPersister(), - isVersionIncrementDisabled() - )); + .thenAccept( v -> { + final Object version = getVersion( getState(), getPersister() ); + final PersistenceContext persistenceContextInternal = getSession().getPersistenceContextInternal(); + final EntityHolder entityHolder = persistenceContextInternal + .addEntityHolder( getEntityKey(), getInstance() ); + final EntityEntry entityEntry = persistenceContextInternal.addEntry( + getInstance(), + ( getPersister().isMutable() ? Status.MANAGED : Status.READ_ONLY ), + getState(), + getRowId(), + getEntityKey().getIdentifier(), + version, + LockMode.WRITE, + isExecuted(), + getPersister(), + isVersionIncrementDisabled() + ); + entityHolder.setEntityEntry( entityEntry ); + if ( isEarlyInsert() ) { + addCollectionsByKeyToPersistenceContext( persistenceContextInternal, getState() ); + } + }); } + void addCollectionsByKeyToPersistenceContext(PersistenceContext persistenceContext, Object[] objects); + default CompletionStage reactiveFindNonNullableTransientEntities() { return ForeignKeys.findNonNullableTransientEntities( getPersister().getEntityName(), getInstance(), getState(), isEarlyInsert(), getSession() ); } AbstractEntityInsertAction asAbstractEntityInsertAction(); - - default int compareActionTo(ReactiveEntityInsertAction delegate) { - return asAbstractEntityInsertAction().compareTo( delegate.asAbstractEntityInsertAction() ); - } - } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityRegularInsertAction.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityRegularInsertAction.java index f3598a09f..062f0374f 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityRegularInsertAction.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactiveEntityRegularInsertAction.java @@ -124,6 +124,11 @@ public EntityKey getEntityKey() { return super.getEntityKey(); } + @Override + public void addCollectionsByKeyToPersistenceContext(PersistenceContext persistenceContext, Object[] objects) { + super.addCollectionsByKeyToPersistenceContext( persistenceContext, objects ); + } + @Override public AbstractEntityInsertAction asAbstractEntityInsertAction() { return this; @@ -135,6 +140,11 @@ protected void markExecuted() { executed = true; } + @Override + public Object getRowId() { + return super.getRowId(); + } + @Override public boolean isExecuted() { return executed; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactivePersistenceContextAdapter.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactivePersistenceContextAdapter.java index 66d0624e0..33750cc0d 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactivePersistenceContextAdapter.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/ReactivePersistenceContextAdapter.java @@ -5,6 +5,11 @@ */ package org.hibernate.reactive.engine.impl; +import java.io.Serializable; +import java.util.HashMap; +import java.util.concurrent.CompletionStage; +import java.util.function.Consumer; + import org.hibernate.HibernateException; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.internal.StatefulPersistenceContext; @@ -13,15 +18,13 @@ import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.persister.entity.impl.ReactiveEntityPersister; import org.hibernate.reactive.session.ReactiveSession; -import java.io.Serializable; -import java.util.HashMap; -import java.util.concurrent.CompletionStage; -import java.util.function.Consumer; - +import static java.lang.invoke.MethodHandles.lookup; import static org.hibernate.pretty.MessageHelper.infoString; +import static org.hibernate.reactive.logging.impl.LoggerFactory.make; import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; @@ -30,6 +33,8 @@ */ public class ReactivePersistenceContextAdapter extends StatefulPersistenceContext { + private static final Log LOG = make( Log.class, lookup() ); + private HashMap entitySnapshotsByKey; /** @@ -71,7 +76,7 @@ public void initializeNonLazyCollections() { @Deprecated @Override public Object[] getDatabaseSnapshot(Object id, EntityPersister persister) throws HibernateException { - throw new UnsupportedOperationException( "reactive persistence context" ); + throw LOG.nonReactiveMethodCall( "reactiveGetDatabaseSnapshot" ); } private static final Object[] NO_ROW = new Object[]{ StatefulPersistenceContext.NO_ROW }; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/AbstractReactiveFlushingEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/AbstractReactiveFlushingEventListener.java index b43d2753c..2772cc15d 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/AbstractReactiveFlushingEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/AbstractReactiveFlushingEventListener.java @@ -5,8 +5,6 @@ */ package org.hibernate.reactive.event.impl; -import static org.hibernate.reactive.util.impl.CompletionStages.loop; - import java.lang.invoke.MethodHandles; import java.util.Map; import java.util.concurrent.CompletionStage; @@ -38,6 +36,9 @@ import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.session.ReactiveSession; +import static org.hibernate.reactive.engine.impl.CascadingActions.PERSIST_ON_FLUSH; +import static org.hibernate.reactive.util.impl.CompletionStages.loop; + /** * Collects commons methods needed during the management of flush events. * @@ -80,14 +81,32 @@ private ReactiveActionQueue actionQueue(EventSource session) { * @throws HibernateException Error flushing caches to execution queues. */ protected CompletionStage flushEverythingToExecutions(FlushEvent event) throws HibernateException { - LOG.trace( "Flushing session" ); - final EventSource session = event.getSession(); - final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); - session.getInterceptor().preFlush( persistenceContext.managedEntitiesIterator() ); + return preFlush( session, persistenceContext ) + .thenRun( () -> flushEverythingToExecutions( event, persistenceContext, session ) ); + } + protected void flushEverythingToExecutions(FlushEvent event, PersistenceContext persistenceContext, EventSource session) { + persistenceContext.setFlushing( true ); + try { + int entityCount = flushEntities( event, persistenceContext ); + int collectionCount = flushCollections( session, persistenceContext ); + + event.setNumberOfEntitiesProcessed( entityCount ); + event.setNumberOfCollectionsProcessed( collectionCount ); + } + finally { + persistenceContext.setFlushing( false); + } + + //some statistics + logFlushResults( event ); + } + + protected CompletionStage preFlush(EventSource session, PersistenceContext persistenceContext) { + session.getInterceptor().preFlush( persistenceContext.managedEntitiesIterator() ); return prepareEntityFlushes( session, persistenceContext ) .thenAccept( v -> { // we could move this inside if we wanted to @@ -97,20 +116,6 @@ protected CompletionStage flushEverythingToExecutions(FlushEvent event) th // now, any collections that are initialized // inside this block do not get updated - they // are ignored until the next flush - persistenceContext.setFlushing( true ); - try { - int entityCount = flushEntities( event, persistenceContext ); - int collectionCount = flushCollections( session, persistenceContext ); - - event.setNumberOfEntitiesProcessed(entityCount); - event.setNumberOfCollectionsProcessed(collectionCount); - } - finally { - persistenceContext.setFlushing( false ); - } - - //some statistics - logFlushResults( event ); } ); } @@ -145,7 +150,6 @@ protected void logFlushResults(FlushEvent event) { * and also apply orphan delete */ private CompletionStage prepareEntityFlushes(EventSource session, PersistenceContext persistenceContext) throws HibernateException { - LOG.debug( "Processing flush-time cascades" ); final PersistContext context = PersistContext.create(); @@ -154,7 +158,41 @@ private CompletionStage prepareEntityFlushes(EventSource session, Persiste return loop( entries, index -> flushable( entries[index].getValue() ), - index -> cascadeOnFlush( session, entries[index].getValue().getPersister(), entries[index].getKey(), context ) ); + index -> cascadeOnFlush( + session, + entries[index].getValue().getPersister(), + entries[index].getKey(), + context + ) + ) + // perform these checks after all cascade persist events have been + // processed, so that all entities which will be persisted are + // persistent when we do the check (I wonder if we could move this + // into Nullability, instead of abusing the Cascade infrastructure) + .thenCompose( v -> loop( + entries, + index -> flushable( entries[index].getValue() ), + index -> Cascade.cascade( + CascadingActions.CHECK_ON_FLUSH, + CascadePoint.BEFORE_FLUSH, + session, + entries[index].getValue().getPersister(), + entries[index].getKey(), + null + ) + ) ); + } + + private CompletionStage cascadeOnFlush( + EventSource session, + EntityPersister persister, + Object object, + PersistContext anything) + throws HibernateException { + final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); + persistenceContext.incrementCascadeLevel(); + return Cascade.cascade( PERSIST_ON_FLUSH, CascadePoint.BEFORE_FLUSH, session, persister, object, anything ) + .whenComplete( (unused, throwable) -> persistenceContext.decrementCascadeLevel() ); } private static boolean flushable(EntityEntry entry) { @@ -299,19 +337,6 @@ private int flushCollections(final EventSource session, final PersistenceContext return count; } - private CompletionStage cascadeOnFlush( - EventSource session, - EntityPersister persister, - Object object, - PersistContext copiedAlready) - throws HibernateException { - return new Cascade<>( - CascadingActions.PERSIST_ON_FLUSH, - CascadePoint.BEFORE_FLUSH, - persister, object, copiedAlready, session - ).cascade(); - } - /** * 1. Recreate the collection key to collection map * 2. rebuild the collection entries 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 54fd9b23a..e9d68b00e 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 @@ -43,6 +43,7 @@ import org.hibernate.type.Type; import org.hibernate.type.TypeHelper; +import static org.hibernate.engine.internal.ManagedTypeHelper.processIfManagedEntity; import static org.hibernate.engine.internal.ManagedTypeHelper.processIfSelfDirtinessTracker; import static org.hibernate.engine.internal.Versioning.getVersion; import static org.hibernate.engine.internal.Versioning.seedVersion; @@ -226,6 +227,7 @@ protected CompletionStage reactivePerformSave( callbackRegistry.preCreate( entity ); processIfSelfDirtinessTracker( entity, SelfDirtinessTracker::$$_hibernate_clearDirtyAttributes ); + processIfManagedEntity( entity, managedEntity -> managedEntity.$$_hibernate_setUseTracker( true ) ); if ( persister.getGenerator() instanceof Assigned ) { id = persister.getIdentifier( entity, source ); @@ -277,6 +279,11 @@ private CompletionStage generateEntityKey(Object id, EntityPersister return failedFuture( new NonUniqueObjectException( id, persister.getEntityName() ) ); } } + else if ( persistenceContext.containsDeletedUnloadedEntityKey( key ) ) { + return source.unwrap( ReactiveSession.class ) + .reactiveForceFlush( persistenceContext.getEntry( old ) ) + .thenApply( v -> key ); + } else { return completedFuture( key ); } @@ -331,6 +338,10 @@ protected CompletionStage reactivePerformSaveOrReplicate( false ); + if ( original.getLoadedState() != null ) { + persistenceContext.getEntityHolder( key ).setEntityEntry( original ); + } + return cascadeBeforeSave( source, persister, entity, context ) .thenCompose( v -> addInsertAction( // We have to do this after cascadeBeforeSave completes, @@ -409,7 +420,7 @@ private CompletionStage addInsertAction( boolean useIdentityColumn, EventSource source, boolean shouldDelayIdentityInserts) { - final ReactiveActionQueue actionQueue = source.unwrap(ReactiveSession.class).getReactiveActionQueue(); + final ReactiveActionQueue actionQueue = source.unwrap( ReactiveSession.class ).getReactiveActionQueue(); if ( useIdentityColumn ) { final ReactiveEntityIdentityInsertAction insert = new ReactiveEntityIdentityInsertAction( values, entity, persister, false, source, shouldDelayIdentityInserts @@ -438,11 +449,14 @@ protected CompletionStage cascadeBeforeSave( Object entity, C context) { // cascade-save to many-to-one BEFORE the parent is saved - return new Cascade<>( + return Cascade.cascade( getCascadeReactiveAction(), CascadePoint.BEFORE_INSERT_AFTER_DELETE, - persister, entity, context, source - ).cascade(); + source, + persister, + entity, + context + ); } /** @@ -459,11 +473,14 @@ protected CompletionStage cascadeAfterSave( Object entity, C context) { // cascade-save to collections AFTER the collection owner was saved - return new Cascade<>( + return Cascade.cascade( getCascadeReactiveAction(), CascadePoint.AFTER_INSERT_BEFORE_DELETE, - persister, entity, context, source - ).cascade(); + source, + persister, + entity, + context + ); } protected abstract CascadingAction getCascadeReactiveAction(); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveDeleteEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveDeleteEventListener.java index 2c7784d7b..66151f03b 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveDeleteEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveDeleteEventListener.java @@ -5,10 +5,6 @@ */ package org.hibernate.reactive.event.impl; -import static org.hibernate.pretty.MessageHelper.infoString; -import static org.hibernate.reactive.engine.impl.Cascade.fetchLazyAssociationsBeforeCascade; -import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; - import java.lang.invoke.MethodHandles; import java.util.concurrent.CompletionStage; @@ -17,6 +13,7 @@ import org.hibernate.TransientObjectException; import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata; +import org.hibernate.classic.Lifecycle; import org.hibernate.engine.internal.CascadePoint; import org.hibernate.engine.internal.Nullability; import org.hibernate.engine.spi.EntityEntry; @@ -57,6 +54,10 @@ import org.hibernate.type.Type; import org.hibernate.type.TypeHelper; +import static org.hibernate.pretty.MessageHelper.infoString; +import static org.hibernate.reactive.engine.impl.Cascade.fetchLazyAssociationsBeforeCascade; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; + /** * A reactive {@link org.hibernate.event.internal.DefaultDeleteEventListener}. */ @@ -82,8 +83,8 @@ public void wasJpaBootstrap(boolean wasJpaBootstrap) { * Handle the given delete event. * * @param event The delete event to be handled. - * @deprecated only the reactive version is supported * @see #reactiveOnDelete(DeleteEvent) + * @deprecated only the reactive version is supported */ @Deprecated @Override @@ -92,8 +93,8 @@ public void onDelete(DeleteEvent event) { } /** - * @deprecated only the reactive version is supported * @see #reactiveOnDelete(DeleteEvent, DeleteContext) + * @deprecated only the reactive version is supported */ @Deprecated @Override @@ -105,7 +106,6 @@ public void onDelete(DeleteEvent event, DeleteContext transientEntities) throws * Handle the given delete event. * * @param event The delete event to be handled. - * */ @Override public CompletionStage reactiveOnDelete(DeleteEvent event) throws HibernateException { @@ -115,26 +115,23 @@ public CompletionStage reactiveOnDelete(DeleteEvent event) throws Hibernat /** * Handle the given delete event. This is the cascaded form. * - * @param event The delete event. + * @param event The delete event. * @param transientEntities The cache of entities already deleted - * */ @Override - public CompletionStage reactiveOnDelete(DeleteEvent event, DeleteContext transientEntities) throws HibernateException { - if ( optimizeUnloadedDelete( event ) ) { - return voidFuture(); - } - else { - final EventSource source = event.getSession(); - Object object = event.getObject(); - if ( object instanceof CompletionStage ) { - final CompletionStage stage = (CompletionStage) object; - return stage.thenCompose( objectEvent -> fetchAndDelete( event, transientEntities, source, objectEvent ) ); - } - else { - return fetchAndDelete( event, transientEntities, source, object); - } - } + public CompletionStage reactiveOnDelete(DeleteEvent event, DeleteContext transientEntities) + throws HibernateException { + return !optimizeUnloadedDelete( event ) + ? fetchAndDelete( event, transientEntities ) + : voidFuture(); + } + + private CompletionStage delete(DeleteEvent event, DeleteContext transientEntities, Object entity) { + final PersistenceContext persistenceContext = event.getSession().getPersistenceContextInternal(); + final EntityEntry entityEntry = persistenceContext.getEntry( entity ); + return entityEntry == null + ? deleteTransientInstance( event, transientEntities, entity ) + : deletePersistentInstance( event, transientEntities, entity, entityEntry ); } private boolean optimizeUnloadedDelete(DeleteEvent event) { @@ -157,12 +154,12 @@ && canBeDeletedWithoutLoading( source, persister ) ) { if ( persister.hasOwnedCollections() ) { // we're deleting an unloaded proxy with collections - for ( Type type : persister.getPropertyTypes() ) { //TODO: when we enable this for subclasses use getSubclassPropertyTypeClosure() + for ( Type type : persister.getPropertyTypes() ) { deleteOwnedCollections( type, id, source ); } } - ((ReactiveSession) source).getReactiveActionQueue() + ( (ReactiveSession) source ).getReactiveActionQueue() .addAction( new ReactiveEntityDeleteAction( id, persister, source ) ); } return true; @@ -174,10 +171,10 @@ && canBeDeletedWithoutLoading( source, persister ) ) { private static void deleteOwnedCollections(Type type, Object key, EventSource session) { final MappingMetamodelImplementor mappingMetamodel = session.getFactory().getMappingMetamodel(); - final ReactiveActionQueue actionQueue = ((ReactiveSession) session).getReactiveActionQueue(); + final ReactiveActionQueue actionQueue = ( (ReactiveSession) session ).getReactiveActionQueue(); if ( type.isCollectionType() ) { final String role = ( (CollectionType) type ).getRole(); - final CollectionPersister persister = mappingMetamodel.getCollectionDescriptor(role); + final CollectionPersister persister = mappingMetamodel.getCollectionDescriptor( role ); if ( !persister.isInverse() ) { actionQueue.addAction( new ReactiveCollectionRemoveAction( persister, key, session ) ); } @@ -190,11 +187,9 @@ else if ( type.isComponentType() ) { } } - private CompletionStage fetchAndDelete( - DeleteEvent event, - DeleteContext transientEntities, - EventSource source, - Object objectEvent) { + private CompletionStage fetchAndDelete(DeleteEvent event, DeleteContext transientEntities) { + final Object objectEvent = event.getObject(); + final EventSource source = event.getSession(); boolean detached = event.getEntityName() != null ? !source.contains( event.getEntityName(), objectEvent ) : !source.contains( objectEvent ); @@ -204,20 +199,21 @@ private CompletionStage fetchAndDelete( } //Object entity = persistenceContext.unproxyAndReassociate( event.getObject() ); + return ( (ReactiveSession) source ) + .reactiveFetch( objectEvent, true ) + .thenCompose( entity -> delete( event, transientEntities, entity ) ); - return ( (ReactiveSession) source ).reactiveFetch( objectEvent, true ) - .thenCompose( entity -> reactiveOnDelete( event, transientEntities, entity ) ); } - private CompletionStage reactiveOnDelete(DeleteEvent event, DeleteContext transientEntities, Object entity) { - final PersistenceContext persistenceContext = event.getSession().getPersistenceContextInternal(); - final EntityEntry entityEntry = persistenceContext.getEntry( entity ); - if ( entityEntry == null ) { - return deleteTransientInstance( event, transientEntities, entity ); - } - else { - return deletePersistentInstance( event, transientEntities, entity, entityEntry ); + protected boolean invokeDeleteLifecycle(EventSource session, Object entity, EntityPersister persister) { + if ( persister.implementsLifecycle() ) { + LOG.debug( "Calling onDelete()" ); + if ( ( (Lifecycle) entity ).onDelete( session ) ) { + LOG.debug( "Deletion vetoed by onDelete()" ); + return true; + } } + return false; } private CompletionStage deleteTransientInstance(DeleteEvent event, DeleteContext transientEntities, Object entity) { @@ -225,7 +221,7 @@ private CompletionStage deleteTransientInstance(DeleteEvent event, DeleteC final EventSource source = event.getSession(); - final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity); + final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity ); return ForeignKeys.isTransient( persister.getEntityName(), entity, null, source.getSession() ) .thenCompose( trans -> { if ( trans ) { @@ -253,7 +249,7 @@ private CompletionStage deleteTransientInstance(DeleteEvent event, DeleteC final EntityEntry entry = persistenceContext.addEntity( entity, persister.isMutable() ? Status.MANAGED : Status.READ_ONLY, - persister.getValues( entity ), + persister.getValues( entity ), key, version, LockMode.NONE, @@ -276,22 +272,12 @@ private CompletionStage deletePersistentInstance( LOG.trace( "Deleting a persistent instance" ); final EventSource source = event.getSession(); if ( entityEntry.getStatus().isDeletedOrGone() - || source.getPersistenceContextInternal() - .containsDeletedUnloadedEntityKey( entityEntry.getEntityKey() ) ) { + || source.getPersistenceContextInternal().containsDeletedUnloadedEntityKey( entityEntry.getEntityKey() ) ) { LOG.trace( "Object was already deleted" ); return voidFuture(); } else { - return delete( - event, - transientEntities, - source, - entity, - entityEntry.getPersister(), - entityEntry.getId(), - entityEntry.getVersion(), - entityEntry - ); + return delete( event, transientEntities, source, entity, entityEntry.getPersister(), entityEntry.getId(), entityEntry.getVersion(), entityEntry ); } } @@ -305,20 +291,23 @@ private CompletionStage delete( Object version, EntityEntry entry) { callbackRegistry.preRemove( entity ); - return deleteEntity( - source, - entity, - entry, - event.isCascadeDeleteEnabled(), - event.isOrphanRemovalBeforeUpdates(), - persister, - transientEntities - ) - .thenAccept( v -> { - if ( source.getFactory().getSessionFactoryOptions().isIdentifierRollbackEnabled() ) { - persister.resetIdentifier( entity, id, version, source ); - } - } ); + if ( !invokeDeleteLifecycle( source, entity, persister ) ) { + return deleteEntity( + source, + entity, + entry, + event.isCascadeDeleteEnabled(), + event.isOrphanRemovalBeforeUpdates(), + persister, + transientEntities + ) + .thenAccept( v -> { + if ( source.getFactory().getSessionFactoryOptions().isIdentifierRollbackEnabled() ) { + persister.resetIdentifier( entity, id, version, source ); + } + } ); + } + return voidFuture(); } /** @@ -326,13 +315,13 @@ private CompletionStage delete( */ private boolean canBeDeletedWithoutLoading(EventSource source, EntityPersister persister) { return source.getInterceptor() == EmptyInterceptor.INSTANCE - && !persister.implementsLifecycle() - && !persister.hasSubclasses() //TODO: should be unnecessary, using EntityPersister.getSubclassPropertyTypeClosure(), etc - && !persister.hasCascadeDelete() - && !persister.hasNaturalIdentifier() - && !persister.hasCollectionNotReferencingPK() - && !hasRegisteredRemoveCallbacks( persister ) - && !hasCustomEventListeners( source ); + && !persister.implementsLifecycle() + && !persister.hasSubclasses() //TODO: should be unnecessary, using EntityPersister.getSubclassPropertyTypeClosure(), etc + && !persister.hasCascadeDelete() + && !persister.hasNaturalIdentifier() + && !persister.hasCollectionNotReferencingPK() + && !hasRegisteredRemoveCallbacks( persister ) + && !hasCustomEventListeners( source ); } private static boolean hasCustomEventListeners(EventSource source) { @@ -340,16 +329,16 @@ private static boolean hasCustomEventListeners(EventSource source) { // Bean Validation adds a PRE_DELETE listener // and Envers adds a POST_DELETE listener return fss.eventListenerGroup_PRE_DELETE.count() > 0 - || fss.eventListenerGroup_POST_DELETE.count() > 1 - || fss.eventListenerGroup_POST_DELETE.count() == 1 - && !(fss.eventListenerGroup_POST_DELETE.listeners().iterator().next() - instanceof PostDeleteEventListenerStandardImpl); + || fss.eventListenerGroup_POST_DELETE.count() > 1 + || fss.eventListenerGroup_POST_DELETE.count() == 1 + && !( fss.eventListenerGroup_POST_DELETE.listeners().iterator().next() + instanceof PostDeleteEventListenerStandardImpl ); } private boolean hasRegisteredRemoveCallbacks(EntityPersister persister) { final Class mappedClass = persister.getMappedClass(); return callbackRegistry.hasRegisteredCallbacks( mappedClass, CallbackType.PRE_REMOVE ) - || callbackRegistry.hasRegisteredCallbacks( mappedClass, CallbackType.POST_REMOVE ); + || callbackRegistry.hasRegisteredCallbacks( mappedClass, CallbackType.POST_REMOVE ); } /** @@ -385,11 +374,11 @@ private void disallowDeletionOfDetached(DeleteEvent event) { * passed to remove operation in which case cascades still need to be * performed. * - * @param session The session which is the source of the event - * @param entity The entity being delete processed - * @param persister The entity persister + * @param session The session which is the source of the event + * @param entity The entity being delete processed + * @param persister The entity persister * @param transientEntities A cache of already visited transient entities - * (to avoid infinite recursion). + * (to avoid infinite recursion). */ protected CompletionStage deleteTransientEntity( EventSource session, @@ -412,12 +401,12 @@ protected CompletionStage deleteTransientEntity( * really perform it; just schedules an action/execution with the * {@link org.hibernate.engine.spi.ActionQueue} for execution during flush. * - * @param session The originating session - * @param entity The entity to delete - * @param entityEntry The entity's entry in the {@link PersistenceContext} + * @param session The originating session + * @param entity The entity to delete + * @param entityEntry The entity's entry in the {@link PersistenceContext} * @param isCascadeDeleteEnabled Is delete cascading enabled? - * @param persister The entity persister. - * @param transientEntities A cache of already deleted entities. + * @param persister The entity persister. + * @param transientEntities A cache of already deleted entities. */ protected CompletionStage deleteEntity( final EventSource session, @@ -442,7 +431,7 @@ protected CompletionStage deleteEntity( final Object[] deletedState = createDeletedState( persister, entity, currentState, session ); entityEntry.setDeletedState( deletedState ); - session.getInterceptor().onDelete( + session.getInterceptor().onRemove( entity, entityEntry.getId(), deletedState, @@ -525,7 +514,7 @@ private Object[] createDeletedState( final String[] propertyNames = persister.getPropertyNames(); final BytecodeEnhancementMetadata enhancementMetadata = persister.getBytecodeEnhancementMetadata(); - for ( int i = 0; i < types.length; i++) { + for ( int i = 0; i < types.length; i++ ) { if ( types[i].isCollectionType() && !enhancementMetadata.isAttributeLoaded( parent, propertyNames[i] ) ) { final CollectionType collectionType = (CollectionType) types[i]; final CollectionPersister collectionDescriptor = persister.getFactory().getMappingMetamodel() @@ -533,7 +522,12 @@ private Object[] createDeletedState( if ( collectionDescriptor.needsRemove() || collectionDescriptor.hasCache() ) { final Object keyOfOwner = collectionType.getKeyOfOwner( parent, eventSource.getSession() ); // This will make sure that a CollectionEntry exists - deletedState[i] = collectionType.getCollection( keyOfOwner, eventSource.getSession(), parent, false ); + deletedState[i] = collectionType.getCollection( + keyOfOwner, + eventSource.getSession(), + parent, + false + ); } else { deletedState[i] = currentState[i]; @@ -556,13 +550,15 @@ protected CompletionStage cascadeBeforeDelete( Object entity, DeleteContext transientEntities) throws HibernateException { // cascade-delete to collections BEFORE the collection owner is deleted - return fetchLazyAssociationsBeforeCascade( CascadingActions.DELETE, persister, entity, session ) - .thenCompose( - v -> new Cascade<>( - CascadingActions.DELETE, - CascadePoint.AFTER_INSERT_BEFORE_DELETE, - persister, entity, transientEntities, session - ).cascade() + return fetchLazyAssociationsBeforeCascade( CascadingActions.REMOVE, persister, entity, session ) + .thenCompose( v -> Cascade.cascade( + CascadingActions.REMOVE, + CascadePoint.AFTER_INSERT_BEFORE_DELETE, + session, + persister, + entity, + transientEntities + ) ); } @@ -572,10 +568,13 @@ protected CompletionStage cascadeAfterDelete( Object entity, DeleteContext transientEntities) throws HibernateException { // cascade-delete to many-to-one AFTER the parent was deleted - return new Cascade<>( - CascadingActions.DELETE, + return Cascade.cascade( + CascadingActions.REMOVE, CascadePoint.BEFORE_INSERT_AFTER_DELETE, - persister, entity, transientEntities, session - ).cascade(); + session, + persister, + entity, + transientEntities + ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveInitializeCollectionEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveInitializeCollectionEventListener.java index 5704f3faf..20466abd2 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveInitializeCollectionEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveInitializeCollectionEventListener.java @@ -27,6 +27,7 @@ import org.hibernate.stat.spi.StatisticsImplementor; import static org.hibernate.pretty.MessageHelper.collectionInfoString; +import static org.hibernate.reactive.util.impl.CompletionStages.failedFuture; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; public class DefaultReactiveInitializeCollectionEventListener implements InitializeCollectionEventListener { @@ -47,7 +48,7 @@ public CompletionStage onReactiveInitializeCollection(InitializeCollection final CollectionEntry ce = source.getPersistenceContextInternal().getCollectionEntry( collection ); if ( ce == null ) { - throw LOG.collectionWasEvicted(); + return failedFuture( LOG.collectionWasEvicted() ); } if ( collection.wasInitialized() ) { @@ -58,10 +59,7 @@ public CompletionStage onReactiveInitializeCollection(InitializeCollection final CollectionPersister loadedPersister = ce.getLoadedPersister(); final Object loadedKey = ce.getLoadedKey(); if ( LOG.isTraceEnabled() ) { - LOG.tracev( - "Initializing collection {0}", - collectionInfoString( loadedPersister, collection, loadedKey, source ) - ); + LOG.tracev( "Initializing collection {0}", collectionInfoString( loadedPersister, collection, loadedKey, source ) ); LOG.trace( "Checking second-level cache" ); } @@ -129,8 +127,7 @@ private boolean initializeCollectionFromCache( PersistentCollection collection, SessionImplementor source) { - if ( source.getLoadQueryInfluencers().hasEnabledFilters() - && persister.isAffectedByEnabledFilters( source ) ) { + if ( source.getLoadQueryInfluencers().hasEnabledFilters() && persister.isAffectedByEnabledFilters( source ) ) { LOG.trace( "Disregarding cached version (if any) of collection due to enabled filters" ); return false; } @@ -160,12 +157,12 @@ private boolean initializeCollectionFromCache( return false; } - final CollectionCacheEntry cacheEntry = (CollectionCacheEntry) - persister.getCacheEntryStructure().destructure( ce, factory ); + final CollectionCacheEntry cacheEntry = (CollectionCacheEntry) persister + .getCacheEntryStructure().destructure( ce, factory ); final PersistenceContext persistenceContext = source.getPersistenceContextInternal(); cacheEntry.assemble( collection, persister, persistenceContext.getCollectionOwner( id, persister ) ); - persistenceContext.getCollectionEntry( collection ).postInitialize( collection ); + persistenceContext.getCollectionEntry( collection ).postInitialize( collection, source ); return true; } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveLockEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveLockEventListener.java index 7a356def1..7832298e0 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveLockEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveLockEventListener.java @@ -113,14 +113,14 @@ private CompletionStage lockEntry( } private CompletionStage cascadeOnLock(LockEvent event, EntityPersister persister, Object entity) { - return new Cascade<>( + return Cascade.cascade( CascadingActions.LOCK, CascadePoint.AFTER_LOCK, + event.getSession(), persister, entity, - event.getLockOptions(), - event.getSession() - ).cascade(); + event.getLockOptions() + ); } /** diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveMergeEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveMergeEventListener.java index 6a23b8d57..7f258a1d4 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveMergeEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveMergeEventListener.java @@ -588,14 +588,14 @@ protected CompletionStage cascadeOnMerge( final Object entity, final MergeContext copyCache ) { - return new Cascade<>( + return Cascade.cascade( getCascadeReactiveAction(), CascadePoint.BEFORE_MERGE, + source, persister, entity, - copyCache, - source - ).cascade(); + copyCache + ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveRefreshEventListener.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveRefreshEventListener.java index a68754536..f5e5eb1b4 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveRefreshEventListener.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/event/impl/DefaultReactiveRefreshEventListener.java @@ -5,9 +5,6 @@ */ package org.hibernate.reactive.event.impl; -import static org.hibernate.pretty.MessageHelper.infoString; -import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; - import java.lang.invoke.MethodHandles; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; @@ -23,7 +20,6 @@ import org.hibernate.engine.internal.CascadePoint; import org.hibernate.engine.spi.ActionQueue; import org.hibernate.engine.spi.EntityEntry; -import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.event.internal.EvictVisitor; @@ -46,6 +42,9 @@ import org.hibernate.type.CompositeType; import org.hibernate.type.Type; +import static org.hibernate.pretty.MessageHelper.infoString; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; + /** * A reactific {@link org.hibernate.event.internal.DefaultRefreshEventListener}. */ @@ -150,11 +149,11 @@ private CompletionStage reactiveOnRefresh(RefreshEvent event, RefreshConte return cascadeRefresh( source, persister, entity, refreshedAlready ) .thenCompose( v -> { if ( entry != null ) { - final EntityKey key = source.generateEntityKey( id, persister ); - persistenceContext.removeEntity( key ); + persistenceContext.removeEntityHolder( entry.getEntityKey() ); if ( persister.hasCollections() ) { - new EvictVisitor( source, entity ).process( entity, persister ); + new EvictVisitor( source, object ).process( object, persister ); } + persistenceContext.removeEntry( object ); } evictEntity( entity, persister, id, source ); @@ -282,14 +281,14 @@ private CompletionStage cascadeRefresh( EntityPersister persister, Object object, RefreshContext refreshedAlready) { - return new Cascade( + return Cascade.cascade( CascadingActions.REFRESH, CascadePoint.BEFORE_REFRESH, + source, persister, object, - refreshedAlready, - source - ).cascade(); + refreshedAlready + ); } private void evictCachedCollections(EntityPersister persister, Object id, EventSource source) { diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/generator/values/internal/ReactiveGeneratedValuesHelper.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/generator/values/internal/ReactiveGeneratedValuesHelper.java index 6501f68b6..8854f22d8 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/generator/values/internal/ReactiveGeneratedValuesHelper.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/generator/values/internal/ReactiveGeneratedValuesHelper.java @@ -14,7 +14,6 @@ import org.hibernate.HibernateException; import org.hibernate.Internal; -import org.hibernate.LockOptions; import org.hibernate.dialect.Dialect; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.generator.EventType; @@ -172,6 +171,7 @@ private static CompletionStage readGeneratedValues( null, null, QueryOptions.NONE, + true, mappingProducer.resolve( directResultSetAccess, session.getLoadQueryInfluencers(), @@ -209,11 +209,10 @@ public boolean shouldReturnProxies() { ); final ReactiveRowReader rowReader = ReactiveResultsHelper.createRowReader( - executionContext, - LockOptions.NONE, + session.getSessionFactory(), RowTransformerArrayImpl.instance(), Object[].class, - jdbcValues.getValuesMapping() + jdbcValues ); final ReactiveRowProcessingState rowProcessingState = new ReactiveRowProcessingState( valuesProcessingState, executionContext, rowReader, jdbcValues ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/BlockingIdentifierGenerator.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/BlockingIdentifierGenerator.java index d15de1779..224410853 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/BlockingIdentifierGenerator.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/BlockingIdentifierGenerator.java @@ -10,9 +10,9 @@ import io.vertx.core.net.impl.pool.CombinerExecutor; import io.vertx.core.net.impl.pool.Executor; import io.vertx.core.net.impl.pool.Task; + import org.hibernate.reactive.id.ReactiveIdentifierGenerator; import org.hibernate.reactive.session.ReactiveConnectionSupplier; -import org.hibernate.reactive.util.impl.CompletionStages; import java.util.Objects; import java.util.concurrent.CompletableFuture; @@ -29,7 +29,6 @@ * @author Gavin King * @author Davide D'Alto * @author Sanne Grinovero - * */ public abstract class BlockingIdentifierGenerator implements ReactiveIdentifierGenerator { @@ -83,40 +82,41 @@ public CompletionStage generate(ReactiveConnectionSupplier connectionSuppl //(this does actually hit a synchronization, but it's extremely short) final long next = next(); if ( next != -1 ) { - return CompletionStages.completedFuture( next ); + return completedFuture( next ); } //Another special case we need to deal with; this is an unlikely configuration, but //if it were to happen we should be better off with direct execution rather than using //the co-operative executor: if ( getBlockSize() <= 1 ) { - return nextHiValue( connectionSupplier ) - .thenApply( i -> next( i ) ); + return nextHiValue( connectionSupplier ).thenApply( this::next ); } final CompletableFuture resultForThisEventLoop = new CompletableFuture<>(); final CompletableFuture result = new CompletableFuture<>(); - executor.submit( new GenerateIdAction( connectionSupplier, result ) ); final Context context = Vertx.currentContext(); - result.whenComplete( (id,t) -> { + executor.submit( new GenerateIdAction( connectionSupplier, result ) ); + result.whenComplete( (id, t) -> { final Context newContext = Vertx.currentContext(); //Need to be careful in resuming processing on the same context as the original //request, potentially having to switch back if we're no longer executing on the same: if ( newContext != context ) { if ( t != null ) { - context.runOnContext( ( v ) -> resultForThisEventLoop.completeExceptionally( t ) ); - } else { - context.runOnContext( ( v ) -> resultForThisEventLoop.complete( id ) ); + context.runOnContext( v -> resultForThisEventLoop.completeExceptionally( t ) ); + } + else { + context.runOnContext( v -> resultForThisEventLoop.complete( id ) ); } } else { if ( t != null ) { resultForThisEventLoop.completeExceptionally( t ); - } else { + } + else { resultForThisEventLoop.complete( id ); } } - }); + } ); return resultForThisEventLoop; } @@ -126,8 +126,8 @@ private final class GenerateIdAction implements Executor.Action private final CompletableFuture result; public GenerateIdAction(ReactiveConnectionSupplier connectionSupplier, CompletableFuture result) { - this.connectionSupplier = Objects.requireNonNull(connectionSupplier); - this.result = Objects.requireNonNull(result); + this.connectionSupplier = Objects.requireNonNull( connectionSupplier ); + this.result = Objects.requireNonNull( result ); } @Override @@ -137,21 +137,22 @@ public Task execute(GeneratorState state) { // We don't need to update or initialize the hi // value in the table, so just increment the lo // value and return the next id in the block - completedFuture( local ) - .whenComplete( this::acceptAsReturnValue ); + completedFuture( local ).whenComplete( this::acceptAsReturnValue ); return null; - } else { + } + else { nextHiValue( connectionSupplier ) .whenComplete( (newlyGeneratedHi, throwable) -> { if ( throwable != null ) { result.completeExceptionally( throwable ); - } else { + } + else { //We ignore the state argument as we actually use the field directly //for convenience, but they are the same object. executor.submit( stateIgnored -> { result.complete( next( newlyGeneratedHi ) ); return null; - }); + } ); } } ); return null; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/ReactiveGeneratorWrapper.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/ReactiveGeneratorWrapper.java index fd7cda2c5..6edfed3df 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/ReactiveGeneratorWrapper.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/ReactiveGeneratorWrapper.java @@ -16,6 +16,8 @@ import java.util.Objects; import java.util.concurrent.CompletionStage; +import static java.util.function.Function.identity; + /** * @author Gavin King */ @@ -46,7 +48,7 @@ public void initialize(SqlStringGenerationContext context) { @Override public CompletionStage generate(ReactiveConnectionSupplier session, Object entity) { - return reactiveGenerator.generate( session, entity ).thenApply( id -> id ); + return reactiveGenerator.generate( session, entity ).thenApply( identity() ); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/ReactiveIdentifierGeneratorFactory.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/ReactiveIdentifierGeneratorFactory.java index 6a8741de0..f1c45e42f 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/ReactiveIdentifierGeneratorFactory.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/id/impl/ReactiveIdentifierGeneratorFactory.java @@ -81,18 +81,6 @@ public Generator createIdentifierGenerator(String strategy, Type type, Propertie throw new MappingException( String.format( "Not an id generator [entity-name=%s]", entityName ) ); } - //TODO: deleteme, after update to ORM - @Override - public Class getIdentifierGeneratorClass(String strategy) { - try { - return super.getIdentifierGeneratorClass( strategy ); - } - catch ( MappingException ignored ) { - // happens because the class does not implement Generator - return generatorClassForName( strategy ); - } - } - protected Class generatorClassForName(String strategy) { try { return serviceRegistry.getService( ClassLoaderService.class ).classForName( strategy ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/DatabaseSnapshotExecutor.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/DatabaseSnapshotExecutor.java index cb71be1b8..3fdd5acf1 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/DatabaseSnapshotExecutor.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/DatabaseSnapshotExecutor.java @@ -44,7 +44,7 @@ import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.FetchParent; import org.hibernate.sql.results.graph.internal.ImmutableFetchList; -import org.hibernate.sql.results.internal.RowTransformerDatabaseSnapshotImpl; +import org.hibernate.sql.results.internal.RowTransformerArrayImpl; import org.hibernate.type.StandardBasicTypes; import org.jboss.logging.Logger; @@ -181,7 +181,13 @@ CompletionStage loadDatabaseSnapshot(Object id, SharedSessionContractI // FIXME: use JdbcServices return StandardReactiveSelectExecutor.INSTANCE - .list( jdbcSelect, jdbcParameterBindings, new BaseExecutionContext( session ), RowTransformerDatabaseSnapshotImpl.instance(), ReactiveListResultsConsumer.UniqueSemantic.FILTER ) + .list( + jdbcSelect, + jdbcParameterBindings, + new BaseExecutionContext( session ), + RowTransformerArrayImpl.instance(), + ReactiveListResultsConsumer.UniqueSemantic.FILTER + ) .thenApply( list -> { assert list != null; final int size = list.size(); @@ -191,7 +197,7 @@ CompletionStage loadDatabaseSnapshot(Object id, SharedSessionContractI return null; } - final Object[] entitySnapshot = (Object[]) list.get( 0 ); + final Object[] entitySnapshot = list.get( 0 ); // The result of this method is treated like the entity state array which doesn't include the id // So we must exclude it from the array if ( entitySnapshot.length == 1 ) { diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdArrayLoadPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdArrayLoadPlan.java index a0f1135a7..c973d9713 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdArrayLoadPlan.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdArrayLoadPlan.java @@ -11,13 +11,15 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.ModelPart; +import org.hibernate.reactive.sql.results.internal.ReactiveRowTransformerArrayImpl; import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.exec.spi.JdbcParametersList; -import org.hibernate.sql.results.internal.RowTransformerDatabaseSnapshotImpl; import org.hibernate.sql.results.spi.RowTransformer; /** * A reactive load plan for loading an array of state by a single restrictive part. + * + * @see org.hibernate.loader.ast.internal.SingleIdArrayLoadPlan */ public class ReactiveSingleIdArrayLoadPlan extends ReactiveSingleIdLoadPlan { @@ -33,7 +35,6 @@ public ReactiveSingleIdArrayLoadPlan( @Override protected RowTransformer> getRowTransformer() { - return RowTransformerDatabaseSnapshotImpl.instance(); + return ReactiveRowTransformerArrayImpl.asRowTransformer(); } - } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdEntityLoaderStandardImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdEntityLoaderStandardImpl.java index bdb9d692a..684894cf2 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdEntityLoaderStandardImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveSingleIdEntityLoaderStandardImpl.java @@ -30,11 +30,11 @@ public class ReactiveSingleIdEntityLoaderStandardImpl extends SingleIdEntityL public ReactiveSingleIdEntityLoaderStandardImpl( EntityMappingType entityDescriptor, - SessionFactoryImplementor sessionFactory) { + LoadQueryInfluencers loadQueryInfluencers) { super( entityDescriptor, - sessionFactory, - (lockOptions, influencers) -> createLoadPlan( entityDescriptor, lockOptions, influencers, sessionFactory ) + loadQueryInfluencers, + (lockOptions, influencers) -> createLoadPlan( entityDescriptor, lockOptions, influencers, influencers.getSessionFactory() ) ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveStandardBatchLoaderFactory.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveStandardBatchLoaderFactory.java index 2fd588727..5d5c03fb5 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveStandardBatchLoaderFactory.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/loader/ast/internal/ReactiveStandardBatchLoaderFactory.java @@ -25,8 +25,10 @@ public class ReactiveStandardBatchLoaderFactory implements BatchLoaderFactory { @Override public EntityBatchLoader createEntityBatchLoader( - int domainBatchSize, EntityMappingType entityDescriptor, - SessionFactoryImplementor factory) { + int domainBatchSize, + EntityMappingType entityDescriptor, + LoadQueryInfluencers loadQueryInfluencers) { + SessionFactoryImplementor factory = loadQueryInfluencers.getSessionFactory(); final Dialect dialect = factory.getJdbcServices().getDialect(); // NOTE : don't use the EntityIdentifierMapping here because it will not be known until later diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactivePluralAttributeMapping.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactivePluralAttributeMapping.java index f7dca4f5c..26a3cbf12 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactivePluralAttributeMapping.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactivePluralAttributeMapping.java @@ -5,16 +5,44 @@ */ package org.hibernate.reactive.metamodel.mapping.internal; +import java.util.function.BiConsumer; +import java.util.function.IntFunction; + +import org.hibernate.cache.MutableCacheKeyBuilder; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.internal.util.IndexedConsumer; +import org.hibernate.metamodel.mapping.AssociationKey; +import org.hibernate.metamodel.mapping.AttributeMapping; +import org.hibernate.metamodel.mapping.BasicValuedModelPart; +import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; +import org.hibernate.metamodel.mapping.JdbcMapping; +import org.hibernate.metamodel.mapping.ManagedMappingType; +import org.hibernate.metamodel.mapping.MappingType; import org.hibernate.metamodel.mapping.PluralAttributeMapping; +import org.hibernate.metamodel.mapping.SelectableConsumer; +import org.hibernate.metamodel.mapping.SelectableMapping; +import org.hibernate.metamodel.mapping.ValuedModelPart; +import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; import org.hibernate.metamodel.mapping.internal.PluralAttributeMappingImpl; +import org.hibernate.metamodel.mapping.internal.SimpleForeignKeyDescriptor; +import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.reactive.sql.results.graph.collection.internal.ReactiveCollectionDomainResult; import org.hibernate.reactive.sql.results.graph.collection.internal.ReactiveEagerCollectionFetch; +import org.hibernate.reactive.sql.results.graph.embeddable.internal.ReactiveEmbeddableForeignKeyResultImpl; import org.hibernate.spi.NavigablePath; +import org.hibernate.sql.ast.spi.SqlAstCreationState; +import org.hibernate.sql.ast.spi.SqlSelection; import org.hibernate.sql.ast.tree.from.TableGroup; +import org.hibernate.sql.ast.tree.from.TableGroupProducer; +import org.hibernate.sql.ast.tree.from.TableReference; +import org.hibernate.sql.ast.tree.predicate.Predicate; import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.DomainResultCreationState; import org.hibernate.sql.results.graph.Fetch; import org.hibernate.sql.results.graph.FetchParent; +import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableForeignKeyResultImpl; +import org.hibernate.type.descriptor.java.JavaType; public class ReactivePluralAttributeMapping extends PluralAttributeMappingImpl implements PluralAttributeMapping { @@ -58,4 +86,524 @@ protected Fetch buildEagerCollectionFetch( creationState ); } + + @Override + public ForeignKeyDescriptor getKeyDescriptor() { + if ( !super.getKeyDescriptor().isEmbedded() ) { + // Hibernate ORM has a check like this that will fail if ForeignKeyDescriptor is not an instance of + // SimpleForeignKeyDescriptor: see LoadSelectBuilder#applySubSelectRestriction line 1115 + return new ReactiveSimpleForeignKeyDescriptor( (SimpleForeignKeyDescriptor) super.getKeyDescriptor() ); + } + return new ReactiveForeignKeyDescriptor( super.getKeyDescriptor() ); + } + + private static DomainResult convert(DomainResult keyDomainResult) { + if ( keyDomainResult instanceof EmbeddableForeignKeyResultImpl ) { + return new ReactiveEmbeddableForeignKeyResultImpl<>( (EmbeddableForeignKeyResultImpl) keyDomainResult ); + } + return keyDomainResult; + } + + private static class ReactiveSimpleForeignKeyDescriptor extends SimpleForeignKeyDescriptor { + + protected ReactiveSimpleForeignKeyDescriptor(SimpleForeignKeyDescriptor original) { + super( original ); + } + + @Override + public DomainResult createKeyDomainResult( + NavigablePath navigablePath, + TableGroup targetTableGroup, + FetchParent fetchParent, + DomainResultCreationState creationState) { + return convert( super.createKeyDomainResult( navigablePath, targetTableGroup, fetchParent, creationState ) ); + } + + @Override + public DomainResult createKeyDomainResult( + NavigablePath navigablePath, + TableGroup targetTableGroup, + Nature fromSide, + FetchParent fetchParent, + DomainResultCreationState creationState) { + return convert( super.createKeyDomainResult( navigablePath, targetTableGroup, fromSide, fetchParent, creationState ) ); + } + } + + private static class ReactiveForeignKeyDescriptor implements ForeignKeyDescriptor { + + private final ForeignKeyDescriptor delegate; + + public ReactiveForeignKeyDescriptor(ForeignKeyDescriptor delegate) { + this.delegate = delegate; + } + + @Override + public String getPartName() { + return delegate.getPartName(); + } + + @Override + public String getKeyTable() { + return delegate.getKeyTable(); + } + + @Override + public String getTargetTable() { + return delegate.getTargetTable(); + } + + @Override + public ValuedModelPart getKeyPart() { + return delegate.getKeyPart(); + } + + @Override + public ValuedModelPart getTargetPart() { + return delegate.getTargetPart(); + } + + @Override + public boolean isKeyPart(ValuedModelPart modelPart) { + return delegate.isKeyPart( modelPart ); + } + + @Override + public ValuedModelPart getPart(Nature nature) { + return delegate.getPart( nature ); + } + + @Override + public Side getKeySide() { + return delegate.getKeySide(); + } + + @Override + public Side getTargetSide() { + return delegate.getTargetSide(); + } + + @Override + public Side getSide(Nature nature) { + return delegate.getSide( nature ); + } + + @Override + public String getContainingTableExpression() { + return delegate.getContainingTableExpression(); + } + + @Override + public int compare(Object key1, Object key2) { + return delegate.compare( key1, key2 ); + } + + @Override + public DomainResult createKeyDomainResult( + NavigablePath navigablePath, + TableGroup targetTableGroup, + FetchParent fetchParent, + DomainResultCreationState creationState) { + return convert( delegate.createKeyDomainResult( + navigablePath, + targetTableGroup, + fetchParent, + creationState + ) ); + } + + @Override + public DomainResult createKeyDomainResult( + NavigablePath navigablePath, + TableGroup targetTableGroup, + Nature fromSide, + FetchParent fetchParent, + DomainResultCreationState creationState) { + return convert( delegate.createKeyDomainResult( + navigablePath, + targetTableGroup, + fromSide, + fetchParent, + creationState + ) ); + } + + @Override + public DomainResult createTargetDomainResult( + NavigablePath navigablePath, + TableGroup targetTableGroup, + FetchParent fetchParent, + DomainResultCreationState creationState) { + return delegate.createTargetDomainResult( navigablePath, targetTableGroup, fetchParent, creationState ); + } + + @Override + public DomainResult createDomainResult( + NavigablePath navigablePath, + TableGroup targetTableGroup, + String resultVariable, + DomainResultCreationState creationState) { + return delegate.createDomainResult( navigablePath, targetTableGroup, resultVariable, creationState ); + } + + @Override + public Predicate generateJoinPredicate( + TableGroup targetSideTableGroup, + TableGroup keySideTableGroup, + SqlAstCreationState creationState) { + return delegate.generateJoinPredicate( targetSideTableGroup, keySideTableGroup, creationState ); + } + + @Override + public Predicate generateJoinPredicate( + TableReference targetSideReference, + TableReference keySideReference, + SqlAstCreationState creationState) { + return delegate.generateJoinPredicate( targetSideReference, keySideReference, creationState ); + } + + @Override + public boolean isSimpleJoinPredicate(Predicate predicate) { + return delegate.isSimpleJoinPredicate( predicate ); + } + + @Override + public SelectableMapping getSelectable(int columnIndex) { + return delegate.getSelectable( columnIndex ); + } + + @Override + public int forEachSelectable(int offset, SelectableConsumer consumer) { + return delegate.forEachSelectable( offset, consumer ); + } + + @Override + public Object getAssociationKeyFromSide( + Object targetObject, + Nature nature, + SharedSessionContractImplementor session) { + return delegate.getAssociationKeyFromSide( targetObject, nature, session ); + } + + @Override + public Object getAssociationKeyFromSide( + Object targetObject, + Side side, + SharedSessionContractImplementor session) { + return delegate.getAssociationKeyFromSide( targetObject, side, session ); + } + + @Override + public int visitKeySelectables(int offset, SelectableConsumer consumer) { + return delegate.visitKeySelectables( offset, consumer ); + } + + @Override + public int visitKeySelectables(SelectableConsumer consumer) { + return delegate.visitKeySelectables( consumer ); + } + + @Override + public int visitTargetSelectables(int offset, SelectableConsumer consumer) { + return delegate.visitTargetSelectables( offset, consumer ); + } + + @Override + public int visitTargetSelectables(SelectableConsumer consumer) { + return delegate.visitTargetSelectables( consumer ); + } + + @Override + public ForeignKeyDescriptor withKeySelectionMapping( + ManagedMappingType declaringType, + TableGroupProducer declaringTableGroupProducer, + IntFunction selectableMappingAccess, + MappingModelCreationProcess creationProcess) { + return delegate.withKeySelectionMapping( + declaringType, + declaringTableGroupProducer, + selectableMappingAccess, + creationProcess + ); + } + + @Override + public ForeignKeyDescriptor withTargetPart(ValuedModelPart targetPart) { + return delegate.withTargetPart( targetPart ); + } + + @Override + public AssociationKey getAssociationKey() { + return delegate.getAssociationKey(); + } + + @Override + public boolean hasConstraint() { + return delegate.hasConstraint(); + } + + @Override + public boolean isEmbedded() { + return delegate.isEmbedded(); + } + + @Override + public boolean isVirtual() { + return delegate.isVirtual(); + } + + @Override + public NavigableRole getNavigableRole() { + return delegate.getNavigableRole(); + } + + @Override + public MappingType getPartMappingType() { + return delegate.getPartMappingType(); + } + + @Override + public JavaType getJavaType() { + return delegate.getJavaType(); + } + + @Override + public boolean isEntityIdentifierMapping() { + return delegate.isEntityIdentifierMapping(); + } + + @Override + public boolean hasPartitionedSelectionMapping() { + return delegate.hasPartitionedSelectionMapping(); + } + + @Override + public void applySqlSelections( + NavigablePath navigablePath, + TableGroup tableGroup, + DomainResultCreationState creationState) { + delegate.applySqlSelections( navigablePath, tableGroup, creationState ); + } + + @Override + public void applySqlSelections( + NavigablePath navigablePath, + TableGroup tableGroup, + DomainResultCreationState creationState, + BiConsumer selectionConsumer) { + delegate.applySqlSelections( navigablePath, tableGroup, creationState, selectionConsumer ); + } + + @Override + public int forEachSelectable(SelectableConsumer consumer) { + return delegate.forEachSelectable( consumer ); + } + + @Override + public AttributeMapping asAttributeMapping() { + return delegate.asAttributeMapping(); + } + + @Override + public EntityMappingType asEntityMappingType() { + return delegate.asEntityMappingType(); + } + + @Override + public BasicValuedModelPart asBasicValuedModelPart() { + return delegate.asBasicValuedModelPart(); + } + + @Override + public int breakDownJdbcValues( + Object domainValue, + JdbcValueConsumer valueConsumer, + SharedSessionContractImplementor session) { + return delegate.breakDownJdbcValues( domainValue, valueConsumer, session ); + } + + @Override + public int breakDownJdbcValues( + Object domainValue, + int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, + SharedSessionContractImplementor session) { + return delegate.breakDownJdbcValues( domainValue, offset, x, y, valueConsumer, session ); + } + + @Override + public int decompose( + Object domainValue, + JdbcValueConsumer valueConsumer, + SharedSessionContractImplementor session) { + return delegate.decompose( domainValue, valueConsumer, session ); + } + + @Override + public int decompose( + Object domainValue, + int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, + SharedSessionContractImplementor session) { + return delegate.decompose( domainValue, offset, x, y, valueConsumer, session ); + } + + @Override + public EntityMappingType findContainingEntityMapping() { + return delegate.findContainingEntityMapping(); + } + + @Override + public boolean areEqual(Object one, Object other, SharedSessionContractImplementor session) { + return delegate.areEqual( one, other, session ); + } + + @Override + public int getJdbcTypeCount() { + return delegate.getJdbcTypeCount(); + } + + @Override + public int forEachJdbcType(IndexedConsumer action) { + return delegate.forEachJdbcType( action ); + } + + @Override + public Object disassemble(Object value, SharedSessionContractImplementor session) { + return delegate.disassemble( value, session ); + } + + @Override + public void addToCacheKey( + MutableCacheKeyBuilder cacheKey, + Object value, + SharedSessionContractImplementor session) { + delegate.addToCacheKey( cacheKey, value, session ); + } + + @Override + public int forEachDisassembledJdbcValue( + Object value, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, + SharedSessionContractImplementor session) { + return delegate.forEachDisassembledJdbcValue( value, x, y, valuesConsumer, session ); + } + + @Override + public int forEachDisassembledJdbcValue( + Object value, + int offset, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, + SharedSessionContractImplementor session) { + return delegate.forEachDisassembledJdbcValue( value, offset, x, y, valuesConsumer, session ); + } + + @Override + public int forEachDisassembledJdbcValue( + Object value, + JdbcValuesConsumer valuesConsumer, + SharedSessionContractImplementor session) { + return delegate.forEachDisassembledJdbcValue( value, valuesConsumer, session ); + } + + @Override + public int forEachDisassembledJdbcValue( + Object value, + int offset, + JdbcValuesConsumer valuesConsumer, + SharedSessionContractImplementor session) { + return delegate.forEachDisassembledJdbcValue( value, offset, valuesConsumer, session ); + } + + @Override + public int forEachJdbcValue( + Object value, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, + SharedSessionContractImplementor session) { + return delegate.forEachJdbcValue( value, x, y, valuesConsumer, session ); + } + + @Override + public int forEachJdbcValue( + Object value, + int offset, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, + SharedSessionContractImplementor session) { + return delegate.forEachJdbcValue( value, offset, x, y, valuesConsumer, session ); + } + + @Override + public int forEachJdbcValue( + Object value, + JdbcValuesConsumer valuesConsumer, + SharedSessionContractImplementor session) { + return delegate.forEachJdbcValue( value, valuesConsumer, session ); + } + + @Override + public int forEachJdbcValue( + Object value, + int offset, + JdbcValuesConsumer valuesConsumer, + SharedSessionContractImplementor session) { + return delegate.forEachJdbcValue( value, offset, valuesConsumer, session ); + } + + @Override + public JdbcMapping getJdbcMapping(int index) { + return delegate.getJdbcMapping( index ); + } + + @Override + public JdbcMapping getSingleJdbcMapping() { + return delegate.getSingleJdbcMapping(); + } + + @Override + public int forEachJdbcType(int offset, IndexedConsumer action) { + return delegate.forEachJdbcType( offset, action ); + } + + @Override + public void forEachInsertable(SelectableConsumer consumer) { + delegate.forEachInsertable( consumer ); + } + + @Override + public void forEachNonFormula(SelectableConsumer consumer) { + delegate.forEachNonFormula( consumer ); + } + + @Override + public void forEachUpdatable(SelectableConsumer consumer) { + delegate.forEachUpdatable( consumer ); + } + + @Override + public MappingType getMappedType() { + return delegate.getMappedType(); + } + + @Override + public JavaType getExpressibleJavaType() { + return delegate.getExpressibleJavaType(); + } + + @Override + public X treatAs(Class targetType) { + return delegate.treatAs( targetType ); + } + } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveToOneAttributeMapping.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveToOneAttributeMapping.java index 09acc9bb9..47b4199de 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveToOneAttributeMapping.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/metamodel/mapping/internal/ReactiveToOneAttributeMapping.java @@ -9,6 +9,7 @@ import org.hibernate.metamodel.mapping.AttributeMetadata; import org.hibernate.metamodel.mapping.ManagedMappingType; import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; +import org.hibernate.reactive.sql.results.graph.embeddable.internal.ReactiveEmbeddableForeignKeyResultImpl; import org.hibernate.reactive.sql.results.graph.entity.internal.ReactiveEntityFetchJoinedImpl; import org.hibernate.reactive.sql.results.graph.entity.internal.ReactiveEntityFetchSelectImpl; import org.hibernate.reactive.sql.results.internal.ReactiveEntityDelayedFetchImpl; @@ -19,6 +20,7 @@ import org.hibernate.sql.results.graph.DomainResultCreationState; import org.hibernate.sql.results.graph.Fetch; import org.hibernate.sql.results.graph.FetchParent; +import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableForeignKeyResultImpl; import org.hibernate.sql.results.graph.entity.EntityFetch; import org.hibernate.sql.results.graph.entity.internal.EntityFetchJoinedImpl; import org.hibernate.sql.results.graph.entity.internal.EntityFetchSelectImpl; @@ -81,16 +83,25 @@ protected EntityFetch buildEntityDelayedFetch( ToOneAttributeMapping fetchedAttribute, NavigablePath navigablePath, DomainResult keyResult, - boolean selectByUniqueKey) { + boolean selectByUniqueKey, + DomainResultCreationState creationState) { return new ReactiveEntityDelayedFetchImpl( fetchParent, fetchedAttribute, navigablePath, - keyResult, - selectByUniqueKey + convert( keyResult ), + selectByUniqueKey, + creationState ); } + private static DomainResult convert(DomainResult keyResult) { + if ( keyResult instanceof EmbeddableForeignKeyResultImpl ) { + return new ReactiveEmbeddableForeignKeyResultImpl<>( (EmbeddableForeignKeyResultImpl) keyResult ); + } + return keyResult; + } + @Override public ReactiveToOneAttributeMapping copy( ManagedMappingType declaringType, diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractPersisterDelegate.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractPersisterDelegate.java index bfb19ea0d..4c1c67d9e 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractPersisterDelegate.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveAbstractPersisterDelegate.java @@ -15,6 +15,7 @@ import org.hibernate.FetchMode; import org.hibernate.LockOptions; +import org.hibernate.engine.FetchTiming; import org.hibernate.engine.spi.CascadeStyle; import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -27,7 +28,9 @@ import org.hibernate.loader.ast.spi.MultiIdLoadOptions; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; +import org.hibernate.mapping.RootClass; import org.hibernate.metamodel.mapping.AttributeMapping; +import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.EntityValuedModelPart; import org.hibernate.metamodel.mapping.ManagedMappingType; @@ -35,6 +38,7 @@ import org.hibernate.metamodel.mapping.internal.GeneratedValuesProcessor; import org.hibernate.metamodel.mapping.internal.MappingModelCreationHelper; import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; +import org.hibernate.metamodel.mapping.internal.NonAggregatedIdentifierMappingImpl; import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.persister.entity.EntityPersister; @@ -54,11 +58,14 @@ import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.metamodel.mapping.internal.ReactivePluralAttributeMapping; import org.hibernate.reactive.metamodel.mapping.internal.ReactiveToOneAttributeMapping; +import org.hibernate.reactive.sql.results.graph.embeddable.internal.ReactiveNonAggregatedIdentifierMappingFetch; import org.hibernate.reactive.sql.results.internal.ReactiveEntityResultImpl; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.DomainResultCreationState; +import org.hibernate.sql.results.graph.Fetch; +import org.hibernate.sql.results.graph.FetchParent; import org.hibernate.type.BasicType; import org.hibernate.type.EntityType; @@ -155,7 +162,7 @@ private static ReactiveSingleIdEntityLoader buildSingleIdEntityLoader( } } - return new ReactiveSingleIdEntityLoaderStandardImpl<>( entityDescriptor, factory ); + return new ReactiveSingleIdEntityLoaderStandardImpl<>( entityDescriptor, new LoadQueryInfluencers( factory ) ); } private static ReactiveSingleIdEntityLoader createBatchingIdEntityLoader( @@ -312,4 +319,45 @@ public AttributeMapping buildPluralAttributeMapping( ReactivePluralAttributeMapping::new ); } + + public EntityIdentifierMapping convertEntityIdentifierMapping(EntityIdentifierMapping entityIdentifierMapping) { + if ( entityIdentifierMapping instanceof NonAggregatedIdentifierMappingImpl ) { + return new ReactiveNonAggregatedIdentifierMappingImpl( (NonAggregatedIdentifierMappingImpl) entityIdentifierMapping ); + } + return entityIdentifierMapping; + } + + private static class ReactiveNonAggregatedIdentifierMappingImpl extends NonAggregatedIdentifierMappingImpl { + + public ReactiveNonAggregatedIdentifierMappingImpl( + EntityPersister entityPersister, + RootClass bootEntityDescriptor, + String rootTableName, + String[] rootTableKeyColumnNames, + MappingModelCreationProcess creationProcess) { + super( entityPersister, bootEntityDescriptor, rootTableName, rootTableKeyColumnNames, creationProcess ); + } + + public ReactiveNonAggregatedIdentifierMappingImpl(NonAggregatedIdentifierMappingImpl entityIdentifierMapping) { + super( entityIdentifierMapping ); + } + + @Override + public Fetch generateFetch( + FetchParent fetchParent, + NavigablePath fetchablePath, + FetchTiming fetchTiming, + boolean selected, + String resultVariable, + DomainResultCreationState creationState) { + return new ReactiveNonAggregatedIdentifierMappingFetch( + fetchablePath, + this, + fetchParent, + fetchTiming, + selected, + creationState + ); + } + } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveJoinedSubclassEntityPersister.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveJoinedSubclassEntityPersister.java index b9bfdfcb2..8eca0a6f4 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveJoinedSubclassEntityPersister.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveJoinedSubclassEntityPersister.java @@ -388,7 +388,7 @@ public CompletionStage reactiveLoadEntityIdByNaturalId(Object[] orderedN } @Override - protected SingleUniqueKeyEntityLoader getUniqueKeyLoader(String attributeName) { + protected SingleUniqueKeyEntityLoader getUniqueKeyLoader(String attributeName, SharedSessionContractImplementor session) { throw LOG.nonReactiveMethodCall( "getReactiveUniqueKeyLoader" ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveSingleTableEntityPersister.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveSingleTableEntityPersister.java index 22454b546..e16523523 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveSingleTableEntityPersister.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveSingleTableEntityPersister.java @@ -8,6 +8,7 @@ import java.sql.PreparedStatement; import java.util.List; import java.util.concurrent.CompletionStage; +import java.util.function.Supplier; import org.hibernate.FetchMode; import org.hibernate.HibernateException; @@ -31,6 +32,7 @@ import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; import org.hibernate.metamodel.mapping.AttributeMapping; +import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.mapping.ManagedMappingType; import org.hibernate.metamodel.mapping.NaturalIdMapping; import org.hibernate.metamodel.mapping.SingularAttributeMapping; @@ -279,6 +281,18 @@ public void merge( throw LOG.nonReactiveMethodCall( "mergeReactive" ); } + @Override + protected EntityIdentifierMapping generateIdentifierMapping( + Supplier templateInstanceCreator, + PersistentClass bootEntityDescriptor, + MappingModelCreationProcess creationProcess) { + return reactiveDelegate.convertEntityIdentifierMapping( super.generateIdentifierMapping( + templateInstanceCreator, + bootEntityDescriptor, + creationProcess + ) ); + } + /** * Process properties generated with an insert * @@ -436,7 +450,7 @@ public CompletionStage reactiveLoadByUniqueKey(String propertyName, Obje } @Override - protected SingleUniqueKeyEntityLoader getUniqueKeyLoader(String attributeName) { + protected SingleUniqueKeyEntityLoader getUniqueKeyLoader(String attributeName, SharedSessionContractImplementor session) { throw LOG.nonReactiveMethodCall( "getReactiveUniqueKeyLoader" ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveUnionSubclassEntityPersister.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveUnionSubclassEntityPersister.java index 7792a27e9..51eb33fad 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveUnionSubclassEntityPersister.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveUnionSubclassEntityPersister.java @@ -210,7 +210,6 @@ public Object insert(Object[] fields, Object object, SharedSessionContractImplem throw LOG.nonReactiveMethodCall( "insertReactive" ); } - /** * @see #insertReactive(Object[], Object, SharedSessionContractImplementor) */ @@ -423,7 +422,7 @@ public CompletionStage reactiveLoadByUniqueKey(String propertyName, Obje } @Override - protected SingleUniqueKeyEntityLoader getUniqueKeyLoader(String attributeName) { + protected SingleUniqueKeyEntityLoader getUniqueKeyLoader(String attributeName, SharedSessionContractImplementor session) { throw new UnsupportedOperationException( "use the reactive method: #getReactiveUniqueKeyLoader(String)" ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ConcreteSqmSelectReactiveQueryPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ConcreteSqmSelectReactiveQueryPlan.java index 858e2d209..821f2c7f3 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ConcreteSqmSelectReactiveQueryPlan.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ConcreteSqmSelectReactiveQueryPlan.java @@ -40,6 +40,7 @@ import org.hibernate.sql.ast.SqlAstTranslator; import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.ast.spi.FromClauseAccess; +import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; @@ -132,22 +133,35 @@ private static CompletionStage executeQueryInterpreter( .thenCompose( subSelectFetchKeyHandler -> session .reactiveAutoFlushIfRequired( jdbcSelect.getAffectedTableNames() ) .thenCompose( required -> StandardReactiveSelectExecutor.INSTANCE - .executeQuery( jdbcSelect, - jdbcParameterBindings, - ConcreteSqmSelectQueryPlan.listInterpreterExecutionContext( hql, executionContext, jdbcSelect, subSelectFetchKeyHandler ), - rowTransformer, - null, - sql -> executionContext.getSession() - .getJdbcCoordinator() - .getStatementPreparer() - .prepareQueryStatement( sql, false, null ), - resultsConsumer + .executeQuery( + jdbcSelect, + jdbcParameterBindings, + ConcreteSqmSelectQueryPlan.listInterpreterExecutionContext( + hql, + executionContext, + jdbcSelect, + subSelectFetchKeyHandler + ), + rowTransformer, + null, + resultCountEstimate( sqmInterpretation, jdbcParameterBindings ), + resultsConsumer ) ) ) .whenComplete( (rs, t) -> domainParameterXref.clearExpansions() ); } + private static int resultCountEstimate( + CacheableSqmInterpretation sqmInterpretation, + JdbcParameterBindings jdbcParameterBindings) { + final Expression fetchExpression = sqmInterpretation.selectStatement.getQueryPart() + .getFetchClauseExpression(); + return fetchExpression != null + ? interpretIntExpression( fetchExpression, jdbcParameterBindings ) + : -1; + } + @Override public ScrollableResultsImplementor performScroll(ScrollMode scrollMode, DomainQueryExecutionContext executionContext) { throw new UnsupportedOperationException(); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java index 587fd95c7..f3a24808d 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveSessionImpl.java @@ -38,12 +38,14 @@ import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.Status; +import org.hibernate.event.service.spi.EventListenerGroup; import org.hibernate.event.spi.AutoFlushEvent; import org.hibernate.event.spi.DeleteContext; import org.hibernate.event.spi.DeleteEvent; import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.FlushEvent; import org.hibernate.event.spi.InitializeCollectionEvent; +import org.hibernate.event.spi.InitializeCollectionEventListener; import org.hibernate.event.spi.LoadEvent; import org.hibernate.event.spi.LoadEventListener; import org.hibernate.event.spi.LockEvent; @@ -689,7 +691,8 @@ public CompletionStage reactiveInitializeCollection(PersistentCollection eventListenerGroupInitCollection = fastSessionServices.eventListenerGroup_INIT_COLLECTION; + return eventListenerGroupInitCollection .fireEventOnEachListener( event, (DefaultReactiveInitializeCollectionEventListener l) -> l::onReactiveInitializeCollection diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/internal/StandardReactiveSelectExecutor.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/internal/StandardReactiveSelectExecutor.java index 51e75108e..bcf33c942 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/internal/StandardReactiveSelectExecutor.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/internal/StandardReactiveSelectExecutor.java @@ -5,17 +5,13 @@ */ package org.hibernate.reactive.sql.exec.internal; -import java.io.Serializable; -import java.sql.PreparedStatement; import java.util.Collection; import java.util.List; import java.util.Set; import java.util.concurrent.CompletionStage; import java.util.concurrent.TimeUnit; -import java.util.function.Function; import org.hibernate.CacheMode; -import org.hibernate.LockOptions; import org.hibernate.cache.spi.QueryKey; import org.hibernate.cache.spi.QueryResultsCache; import org.hibernate.engine.spi.PersistenceContext; @@ -36,14 +32,16 @@ import org.hibernate.reactive.sql.results.spi.ReactiveValuesMappingProducer; import org.hibernate.sql.exec.SqlExecLogger; import org.hibernate.sql.exec.internal.JdbcExecHelper; +import org.hibernate.sql.exec.internal.StandardStatementCreator; import org.hibernate.sql.exec.spi.ExecutionContext; import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; +import org.hibernate.sql.exec.spi.JdbcSelectExecutor; import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.internal.RowTransformerStandardImpl; import org.hibernate.sql.results.internal.RowTransformerTupleTransformerAdapter; +import org.hibernate.sql.results.jdbc.internal.CachedJdbcValuesMetadata; import org.hibernate.sql.results.jdbc.internal.JdbcValuesSourceProcessingStateStandardImpl; -import org.hibernate.sql.results.jdbc.spi.JdbcValuesMapping; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions; import org.hibernate.sql.results.spi.RowTransformer; @@ -78,19 +76,63 @@ public CompletionStage> list( RowTransformer rowTransformer, Class domainResultType, ReactiveListResultsConsumer.UniqueSemantic uniqueSemantic) { - return executeQuery( + return list( jdbcSelect, jdbcParameterBindings, executionContext, rowTransformer, domainResultType, - executionContext.getSession() - .getJdbcCoordinator() - .getStatementPreparer()::prepareStatement, + uniqueSemantic, + -1 + ); + } + + /** + * @since 2.4 (and ORM 6.6) + */ + public CompletionStage> list( + JdbcOperationQuerySelect jdbcSelect, + JdbcParameterBindings jdbcParameterBindings, + ExecutionContext executionContext, + RowTransformer rowTransformer, + Class requestedJavaType, + ReactiveListResultsConsumer.UniqueSemantic uniqueSemantic, + int resultCountEstimate) { + // Only do auto flushing for top level queries + return executeQuery( + jdbcSelect, + jdbcParameterBindings, + executionContext, + rowTransformer, + requestedJavaType, + resultCountEstimate, ReactiveListResultsConsumer.instance( uniqueSemantic ) ); } + /** + * @since 2.4 (and Hibernate ORM 6.6) + */ + public CompletionStage executeQuery( + JdbcOperationQuerySelect jdbcSelect, + JdbcParameterBindings jdbcParameterBindings, + ExecutionContext executionContext, + RowTransformer rowTransformer, + Class domainResultType, + int resultCountEstimate, + ReactiveResultsConsumer resultsConsumer) { + return executeQuery( + jdbcSelect, + jdbcParameterBindings, + executionContext, + rowTransformer, + domainResultType, + resultCountEstimate, + StandardStatementCreator.getStatementCreator( null ), + resultsConsumer + ); + } + @Override public CompletionStage executeQuery( JdbcOperationQuerySelect jdbcSelect, @@ -98,7 +140,8 @@ public CompletionStage executeQuery( ExecutionContext executionContext, RowTransformer rowTransformer, Class domainResultType, - Function statementCreator, + int resultCountEstimate, + JdbcSelectExecutor.StatementCreator statementCreator, ReactiveResultsConsumer resultsConsumer) { final PersistenceContext persistenceContext = executionContext.getSession().getPersistenceContext(); @@ -110,7 +153,7 @@ public CompletionStage executeQuery( persistenceContext.setDefaultReadOnly( readOnly ); } - return doExecuteQuery( jdbcSelect, jdbcParameterBindings, executionContext, rowTransformer, domainResultType, statementCreator, resultsConsumer ) + return doExecuteQuery( jdbcSelect, jdbcParameterBindings, executionContext, rowTransformer, domainResultType, resultCountEstimate, statementCreator, resultsConsumer ) .thenCompose( list -> ( (ReactivePersistenceContextAdapter) persistenceContext ) // only initialize non-lazy collections after everything else has been refreshed .reactiveInitializeNonLazyCollections() @@ -129,10 +172,11 @@ private CompletionStage doExecuteQuery( ExecutionContext executionContext, RowTransformer transformer, Class domainResultType, - Function statementCreator, + int resultCountEstimate, + JdbcSelectExecutor.StatementCreator statementCreator, ReactiveResultsConsumer resultsConsumer) { - final ReactiveDeferredResultSetAccess deferredResultSetAccess = new ReactiveDeferredResultSetAccess( jdbcSelect, jdbcParameterBindings, executionContext, statementCreator ); + final ReactiveDeferredResultSetAccess deferredResultSetAccess = new ReactiveDeferredResultSetAccess( jdbcSelect, jdbcParameterBindings, executionContext, statementCreator, resultCountEstimate ); return resolveJdbcValuesSource( executionContext.getQueryIdentifier( deferredResultSetAccess.getFinalSql() ), @@ -175,17 +219,10 @@ public boolean shouldReturnProxies() { ); final ReactiveRowReader rowReader = ReactiveResultsHelper.createRowReader( - executionContext, - // If follow-on locking is used, we must omit the lock options here, - // because these lock options are only for Initializers. - // If we wouldn't omit this, the follow-on lock requests would be no-ops, - // because the EntityEntries would already have the desired lock mode - deferredResultSetAccess.usesFollowOnLocking() - ? LockOptions.NONE - : executionContext.getQueryOptions().getLockOptions(), + executionContext.getSession().getSessionFactory(), rowTransformer, domainResultType, - jdbcValues.getValuesMapping() + jdbcValues ); final ReactiveRowProcessingState rowProcessingState = new ReactiveRowProcessingState( @@ -195,6 +232,8 @@ public boolean shouldReturnProxies() { jdbcValues ); + rowReader.startLoading( rowProcessingState ); + return resultsConsumer .consume( jdbcValues, @@ -237,7 +276,12 @@ private static RowTransformer rowTransformer( return rowTransformer; } - public CompletionStage resolveJdbcValuesSource(String queryIdentifier, JdbcOperationQuerySelect jdbcSelect, boolean canBeCached, ExecutionContext executionContext, ReactiveResultSetAccess resultSetAccess) { + public CompletionStage resolveJdbcValuesSource( + String queryIdentifier, + JdbcOperationQuerySelect jdbcSelect, + boolean canBeCached, + ExecutionContext executionContext, + ReactiveDeferredResultSetAccess resultSetAccess) { final SharedSessionContractImplementor session = executionContext.getSession(); final SessionFactoryImplementor factory = session.getFactory(); final boolean queryCacheEnabled = factory.getSessionFactoryOptions().isQueryCacheEnabled(); @@ -252,7 +296,7 @@ public CompletionStage resolveJdbcValuesSource(String q if ( cacheable && cacheMode.isGetEnabled() ) { SqlExecLogger.SQL_EXEC_LOGGER.debugf( "Reading Query result cache data per CacheMode#isGetEnabled [%s]", cacheMode.name() ); final Set querySpaces = jdbcSelect.getAffectedTableNames(); - if ( querySpaces == null || querySpaces.size() == 0 ) { + if ( querySpaces == null || querySpaces.isEmpty() ) { SqlExecLogger.SQL_EXEC_LOGGER.tracef( "Unexpected querySpaces is empty" ); } else { @@ -313,41 +357,70 @@ public CompletionStage resolveJdbcValuesSource(String q if ( queryResultsCacheKey == null ) { return mappingProducer .reactiveResolve( resultSetAccess, session.getLoadQueryInfluencers(), factory ) - .thenApply( jdbcValuesMapping -> new ReactiveValuesResultSet( resultSetAccess, queryResultsCacheKey, queryIdentifier, executionContext.getQueryOptions(), jdbcValuesMapping, null, executionContext ) ); + .thenApply( jdbcValuesMapping -> new ReactiveValuesResultSet( + resultSetAccess, + null, + queryIdentifier, + executionContext.getQueryOptions(), + resultSetAccess.usesFollowOnLocking(), + jdbcValuesMapping, + null, + executionContext + ) ); } else { // If we need to put the values into the cache, we need to be able to capture the JdbcValuesMetadata final CapturingJdbcValuesMetadata capturingMetadata = new CapturingJdbcValuesMetadata( resultSetAccess ); - JdbcValuesMetadata metadataForCache = capturingMetadata.resolveMetadataForCache(); return mappingProducer .reactiveResolve( resultSetAccess, session.getLoadQueryInfluencers(), factory ) - .thenApply( jdbcValuesMapping -> new ReactiveValuesResultSet( resultSetAccess, queryResultsCacheKey, queryIdentifier, executionContext.getQueryOptions(), jdbcValuesMapping, metadataForCache, executionContext ) ); + .thenApply( jdbcValuesMapping -> new ReactiveValuesResultSet( + resultSetAccess, + queryResultsCacheKey, + queryIdentifier, + executionContext.getQueryOptions(), + resultSetAccess.usesFollowOnLocking(), + jdbcValuesMapping, + capturingMetadata.resolveMetadataForCache(), + executionContext + ) ); } } else { + // TODO: Implements JdbcValuesCacheHit for reactive, see JdbcSelectExecutorStandardImpl#resolveJdbcValuesSource // If we need to put the values into the cache, we need to be able to capture the JdbcValuesMetadata final CapturingJdbcValuesMetadata capturingMetadata = new CapturingJdbcValuesMetadata( resultSetAccess ); - final CompletionStage stage; if ( cachedResults.isEmpty() || !( cachedResults.get( 0 ) instanceof JdbcValuesMetadata ) ) { - stage = mappingProducer.reactiveResolve( resultSetAccess, session.getLoadQueryInfluencers(), factory ); + return mappingProducer.reactiveResolve( resultSetAccess, session.getLoadQueryInfluencers(), factory ) + .thenApply( jdbcValuesMapping -> new ReactiveValuesResultSet( + resultSetAccess, + queryResultsCacheKey, + queryIdentifier, + executionContext.getQueryOptions(), + resultSetAccess.usesFollowOnLocking(), + jdbcValuesMapping, + capturingMetadata.resolveMetadataForCache(), + executionContext + ) ); } else { - stage = mappingProducer.reactiveResolve( (JdbcValuesMetadata) cachedResults.get( 0 ), session.getLoadQueryInfluencers(), factory ); + return mappingProducer + .reactiveResolve( (JdbcValuesMetadata) cachedResults.get( 0 ), session.getLoadQueryInfluencers(), factory ) + .thenApply( jdbcValuesMapping -> new ReactiveValuesResultSet( + resultSetAccess, + queryResultsCacheKey, + queryIdentifier, + executionContext.getQueryOptions(), + resultSetAccess.usesFollowOnLocking(), + jdbcValuesMapping, + capturingMetadata.resolveMetadataForCache(), + executionContext + ) ); } - return stage.thenApply( jdbcValuesMapping -> new ReactiveValuesResultSet( - resultSetAccess, - queryResultsCacheKey, - queryIdentifier, - executionContext.getQueryOptions(), - jdbcValuesMapping, - capturingMetadata, - executionContext - ) ); } } /** - * see {@link org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.CapturingJdbcValuesMetadata} + * see {@link CachedJdbcValuesMetadata} */ public static class CapturingJdbcValuesMetadata implements JdbcValuesMetadata { private final ReactiveResultSetAccess resultSetAccess; @@ -423,7 +496,7 @@ public BasicType resolveType( return basicType; } - public JdbcValuesMetadata resolveMetadataForCache() { + public CachedJdbcValuesMetadata resolveMetadataForCache() { if ( columnNames == null ) { return null; } @@ -431,60 +504,6 @@ public JdbcValuesMetadata resolveMetadataForCache() { } } - private static class CachedJdbcValuesMetadata implements JdbcValuesMetadata, Serializable { - private final String[] columnNames; - private final BasicType[] types; - - public CachedJdbcValuesMetadata(String[] columnNames, BasicType[] types) { - this.columnNames = columnNames; - this.types = types; - } - - @Override - public int getColumnCount() { - return columnNames.length; - } - - @Override - public int resolveColumnPosition(String columnName) { - final int position = ArrayHelper.indexOf( columnNames, columnName ) + 1; - if ( position == 0 ) { - throw new IllegalStateException( "Unexpected resolving of unavailable column: " + columnName ); - } - return position; - } - - @Override - public String resolveColumnName(int position) { - final String name = columnNames[position - 1]; - if ( name == null ) { - throw new IllegalStateException( "Unexpected resolving of unavailable column at position: " + position ); - } - return name; - } - - @Override - public BasicType resolveType( - int position, - JavaType explicitJavaType, - TypeConfiguration typeConfiguration) { - final BasicType type = types[position - 1]; - if ( type == null ) { - throw new IllegalStateException( "Unexpected resolving of unavailable column at position: " + position ); - } - if ( explicitJavaType == null || type.getJavaTypeDescriptor() == explicitJavaType ) { - //noinspection unchecked - return (BasicType) type; - } - else { - return typeConfiguration.getBasicTypeRegistry().resolve( - explicitJavaType, - type.getJdbcType() - ); - } - } - } - private static class Statistics { private final ExecutionContext executionContext; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/spi/ReactiveRowProcessingState.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/spi/ReactiveRowProcessingState.java index 034a92aa6..09ecd0b10 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/spi/ReactiveRowProcessingState.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/spi/ReactiveRowProcessingState.java @@ -5,18 +5,14 @@ */ package org.hibernate.reactive.sql.exec.spi; -import java.lang.invoke.MethodHandles; import java.util.concurrent.CompletionStage; +import org.hibernate.LockMode; import org.hibernate.query.spi.QueryOptions; -import org.hibernate.reactive.logging.impl.Log; -import org.hibernate.reactive.logging.impl.LoggerFactory; -import org.hibernate.reactive.sql.results.internal.ReactiveInitializersList; import org.hibernate.reactive.sql.results.spi.ReactiveRowReader; -import org.hibernate.spi.NavigablePath; import org.hibernate.sql.exec.internal.BaseExecutionContext; import org.hibernate.sql.exec.spi.ExecutionContext; -import org.hibernate.sql.results.graph.Initializer; +import org.hibernate.sql.results.graph.InitializerData; import org.hibernate.sql.results.graph.entity.EntityFetch; import org.hibernate.sql.results.jdbc.internal.JdbcValuesSourceProcessingStateStandardImpl; import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingState; @@ -29,15 +25,14 @@ */ public class ReactiveRowProcessingState extends BaseExecutionContext implements RowProcessingState { - private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); - private final JdbcValuesSourceProcessingStateStandardImpl resultSetProcessingState; - private final ReactiveInitializersList initializers; - private final ReactiveRowReader rowReader; private final ReactiveValuesResultSet jdbcValues; private final ExecutionContext executionContext; + private final boolean needsResolveState; + + private final InitializerData[] initializerData; public ReactiveRowProcessingState( JdbcValuesSourceProcessingStateStandardImpl resultSetProcessingState, @@ -48,8 +43,10 @@ public ReactiveRowProcessingState( this.resultSetProcessingState = resultSetProcessingState; this.executionContext = executionContext; this.rowReader = rowReader; - this.initializers = rowReader.getReactiveInitializersList(); this.jdbcValues = jdbcValues; + this.needsResolveState = !isQueryCacheHit() + && getQueryOptions().isResultCachingEnabled() == Boolean.TRUE; + this.initializerData = new InitializerData[rowReader.getInitializerCount()]; } public CompletionStage next() { @@ -61,6 +58,42 @@ public JdbcValuesSourceProcessingState getJdbcValuesSourceProcessingState() { return resultSetProcessingState; } + @Override + public Object getEntityId() { + return executionContext.getEntityId(); + } + + @Override + public LockMode determineEffectiveLockMode(String alias) { + if ( jdbcValues.usesFollowOnLocking() ) { + // If follow-on locking is used, we must omit the lock options here, + // because these lock options are only for Initializers. + // If we wouldn't omit this, the follow-on lock requests would be no-ops, + // because the EntityEntrys would already have the desired lock mode + return LockMode.NONE; + } + final LockMode effectiveLockMode = resultSetProcessingState.getQueryOptions().getLockOptions() + .getEffectiveLockMode( alias ); + return effectiveLockMode == LockMode.NONE + ? jdbcValues.getValuesMapping().determineDefaultLockMode( alias, effectiveLockMode ) + : effectiveLockMode; + } + + @Override + public boolean needsResolveState() { + return needsResolveState; + } + + @Override + public T getInitializerData(int initializerId) { + return (T) initializerData[initializerId]; + } + + @Override + public void setInitializerData(int initializerId, InitializerData state) { + initializerData[initializerId] = state; + } + @Override public RowReader getRowReader() { return rowReader; @@ -82,19 +115,12 @@ public boolean isQueryCacheHit() { return false; } - public void finishRowProcessing() { - } - @Override - public Initializer resolveInitializer(NavigablePath path) { - return this.initializers.resolveInitializer( path ); + public void finishRowProcessing(boolean wasAdded) { + jdbcValues.finishRowProcessing( this, wasAdded ); } public QueryOptions getQueryOptions() { return this.executionContext.getQueryOptions(); } - - public boolean hasCollectionInitializers() { - return this.initializers.hasCollectionInitializers(); - } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/spi/ReactiveSelectExecutor.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/spi/ReactiveSelectExecutor.java index b41cd90ab..ab68e34be 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/spi/ReactiveSelectExecutor.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/spi/ReactiveSelectExecutor.java @@ -5,16 +5,16 @@ */ package org.hibernate.reactive.sql.exec.spi; -import java.sql.PreparedStatement; import java.util.List; import java.util.concurrent.CompletionStage; -import java.util.function.Function; import org.hibernate.reactive.sql.results.spi.ReactiveListResultsConsumer; import org.hibernate.reactive.sql.results.spi.ReactiveResultsConsumer; +import org.hibernate.sql.exec.internal.StandardStatementCreator; import org.hibernate.sql.exec.spi.ExecutionContext; import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; +import org.hibernate.sql.exec.spi.JdbcSelectExecutor; import org.hibernate.sql.results.spi.RowTransformer; /** @@ -22,13 +22,37 @@ */ public interface ReactiveSelectExecutor { + /** + * @since 2.4 (and Hibernate ORM 6.6) + */ + default CompletionStage executeQuery( + JdbcOperationQuerySelect jdbcSelect, + JdbcParameterBindings jdbcParameterBindings, + ExecutionContext executionContext, + RowTransformer rowTransformer, + Class domainResultType, + int resultCountEstimate, + ReactiveResultsConsumer resultsConsumer) { + return executeQuery( + jdbcSelect, + jdbcParameterBindings, + executionContext, + rowTransformer, + domainResultType, + resultCountEstimate, + StandardStatementCreator.getStatementCreator( null ), + resultsConsumer + ); + } + CompletionStage executeQuery( JdbcOperationQuerySelect jdbcSelect, JdbcParameterBindings jdbcParameterBindings, ExecutionContext executionContext, RowTransformer rowTransformer, Class domainResultType, - Function statementCreator, + int resultCountEstimate, + JdbcSelectExecutor.StatementCreator statementCreator, ReactiveResultsConsumer resultsConsumer); CompletionStage> list( diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/spi/ReactiveValuesResultSet.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/spi/ReactiveValuesResultSet.java index 4323e04bd..f1dfa276b 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/spi/ReactiveValuesResultSet.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/exec/spi/ReactiveValuesResultSet.java @@ -20,6 +20,7 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.exception.DataException; import org.hibernate.exception.LockTimeoutException; +import org.hibernate.query.spi.Limit; import org.hibernate.query.spi.QueryOptions; import org.hibernate.reactive.sql.results.internal.ReactiveResultSetAccess; import org.hibernate.sql.ast.spi.SqlSelection; @@ -28,8 +29,8 @@ import org.hibernate.sql.results.caching.QueryCachePutManager; import org.hibernate.sql.results.caching.internal.QueryCachePutManagerEnabledImpl; import org.hibernate.sql.results.graph.DomainResult; +import org.hibernate.sql.results.jdbc.internal.CachedJdbcValuesMetadata; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMapping; -import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; import org.hibernate.sql.results.jdbc.spi.RowProcessingState; import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; @@ -46,6 +47,8 @@ public class ReactiveValuesResultSet { private final ReactiveResultSetAccess resultSetAccess; private final JdbcValuesMapping valuesMapping; private final ExecutionContext executionContext; + private final boolean usesFollowOnLocking; + private final int resultCountEstimate; private final SqlSelection[] sqlSelections; private final BitSet initializedIndexes; private final Object[] currentRowJdbcValues; @@ -54,19 +57,29 @@ public class ReactiveValuesResultSet { // Contains the size of the row to cache, or if the value is negative, // represents the inverted index of the single value to cache private final int rowToCacheSize; + private int resultCount; public ReactiveValuesResultSet( ReactiveResultSetAccess resultSetAccess, QueryKey queryCacheKey, String queryIdentifier, QueryOptions queryOptions, + boolean usesFollowOnLocking, JdbcValuesMapping valuesMapping, - JdbcValuesMetadata metadataForCache, + CachedJdbcValuesMetadata metadataForCache, ExecutionContext executionContext) { - this.queryCachePutManager = resolveQueryCachePutManager( executionContext, queryOptions, queryCacheKey, queryIdentifier, metadataForCache ); + this.queryCachePutManager = resolveQueryCachePutManager( + executionContext, + queryOptions, + queryCacheKey, + queryIdentifier, + metadataForCache + ); this.resultSetAccess = resultSetAccess; this.valuesMapping = valuesMapping; this.executionContext = executionContext; + this.usesFollowOnLocking = usesFollowOnLocking; + this.resultCountEstimate = determineResultCountEstimate( resultSetAccess, queryOptions ); final int rowSize = valuesMapping.getRowSize(); this.sqlSelections = new SqlSelection[rowSize]; @@ -116,12 +129,27 @@ public ReactiveValuesResultSet( } } + private int determineResultCountEstimate( + ReactiveResultSetAccess resultSetAccess, + QueryOptions queryOptions) { + final Limit limit = queryOptions.getLimit(); + if ( limit != null && limit.getMaxRows() != null ) { + return limit.getMaxRows(); + } + + final int resultCountEstimate = resultSetAccess.getResultCountEstimate(); + if ( resultCountEstimate > 0 ) { + return resultCountEstimate; + } + return -1; + } + private static QueryCachePutManager resolveQueryCachePutManager( ExecutionContext executionContext, QueryOptions queryOptions, QueryKey queryCacheKey, String queryIdentifier, - JdbcValuesMetadata metadataForCache) { + CachedJdbcValuesMetadata metadataForCache) { if ( queryCacheKey != null ) { final SessionFactoryImplementor factory = executionContext.getSession().getFactory(); final QueryResultsCache queryCache = factory.getCache() @@ -182,13 +210,20 @@ public Object[] getCurrentRowValuesArray() { public void finishUp(SharedSessionContractImplementor session) { if ( queryCachePutManager != null ) { - queryCachePutManager.finishUp( session ); + queryCachePutManager.finishUp( resultCount, session ); } resultSetAccess.release(); } + public boolean usesFollowOnLocking() { + return usesFollowOnLocking; + } + public void finishRowProcessing(RowProcessingState rowProcessingState, boolean wasAdded) { if ( queryCachePutManager != null ) { + if ( wasAdded ) { + resultCount++; + } final Object objectToCache; if ( valueIndexesToCacheIndexes == null ) { objectToCache = Arrays.copyOf( currentRowJdbcValues, currentRowJdbcValues.length ); @@ -246,4 +281,8 @@ private CompletionStage readCurrentRowValues(boolean hasResults) { return true; } ); } + + public int getResultCountEstimate() { + return resultCountEstimate; + } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/ReactiveDomainResultsAssembler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/ReactiveDomainResultsAssembler.java index 84bef8045..8580a8b68 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/ReactiveDomainResultsAssembler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/ReactiveDomainResultsAssembler.java @@ -21,7 +21,7 @@ CompletionStage reactiveAssemble( JdbcValuesSourceProcessingOptions options); /** - * Convenience form of {@link #assemble(RowProcessingState, JdbcValuesSourceProcessingOptions)} + * Convenience form of {@link #assemble(RowProcessingState)} */ default CompletionStage reactiveAssemble(ReactiveRowProcessingState rowProcessingState) { return reactiveAssemble( diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/ReactiveInitializer.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/ReactiveInitializer.java index bff508832..c20b4775d 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/ReactiveInitializer.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/ReactiveInitializer.java @@ -6,18 +6,62 @@ package org.hibernate.reactive.sql.results.graph; import java.util.concurrent.CompletionStage; +import java.util.function.BiFunction; import org.hibernate.Incubating; import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; +import org.hibernate.sql.results.graph.Initializer; +import org.hibernate.sql.results.graph.InitializerData; +import org.hibernate.sql.results.jdbc.spi.RowProcessingState; + /** * @see org.hibernate.sql.results.graph.Initializer */ @Incubating -public interface ReactiveInitializer { +public interface ReactiveInitializer { + + /** + * The current data of this initializer. + */ + Data getData(RowProcessingState rowProcessingState); + + CompletionStage reactiveResolveInstance(Data data); + + /** + * @see org.hibernate.sql.results.graph.internal.AbstractInitializer#resolveKey(InitializerData) + */ + default CompletionStage reactiveResolveKey(Data data) { + data.setState( Initializer.State.KEY_RESOLVED ); + return forEachReactiveSubInitializer( ReactiveInitializer::reactiveResolveKey, data ); + } + + default CompletionStage reactiveResolveKey(RowProcessingState rowProcessingState) { + Data data = getData( rowProcessingState ); + return reactiveResolveKey( data ); + } + + default CompletionStage reactiveResolveInstance(Object instance, Data data) { + return reactiveResolveKey( data ); + } + + default CompletionStage reactiveResolveInstance(Object instance, RowProcessingState rowProcessingState) { + return reactiveResolveInstance( instance, getData( rowProcessingState ) ); + } + + default CompletionStage reactiveResolveInstance(ReactiveRowProcessingState rowProcessingState) { + return reactiveResolveInstance( getData( rowProcessingState ) ); + } + + CompletionStage reactiveInitializeInstance(Data data); - CompletionStage reactiveResolveInstance(ReactiveRowProcessingState rowProcessingState); + default CompletionStage reactiveInitializeInstance(ReactiveRowProcessingState rowProcessingState) { + return reactiveInitializeInstance( getData( rowProcessingState ) ); + } - CompletionStage reactiveInitializeInstance(ReactiveRowProcessingState rowProcessingState); + CompletionStage forEachReactiveSubInitializer( + BiFunction, RowProcessingState, CompletionStage> consumer, + InitializerData data); + Object getResolvedInstance(Data data); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/collection/internal/ReactiveCollectionDomainResult.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/collection/internal/ReactiveCollectionDomainResult.java index 6a60e89db..f1bb6fa9c 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/collection/internal/ReactiveCollectionDomainResult.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/collection/internal/ReactiveCollectionDomainResult.java @@ -5,16 +5,22 @@ */ package org.hibernate.reactive.sql.results.graph.collection.internal; +import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.FetchTiming; import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.reactive.sql.results.graph.entity.internal.ReactiveEntityFetchJoinedImpl; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.ast.tree.from.TableGroup; +import org.hibernate.sql.results.graph.AssemblerCreationState; import org.hibernate.sql.results.graph.DomainResultCreationState; import org.hibernate.sql.results.graph.Fetch; import org.hibernate.sql.results.graph.Fetchable; +import org.hibernate.sql.results.graph.InitializerData; +import org.hibernate.sql.results.graph.InitializerParent; +import org.hibernate.sql.results.graph.collection.CollectionInitializer; import org.hibernate.sql.results.graph.collection.internal.CollectionDomainResult; import org.hibernate.sql.results.graph.entity.internal.EntityFetchJoinedImpl; +import org.hibernate.sql.results.jdbc.spi.RowProcessingState; public class ReactiveCollectionDomainResult extends CollectionDomainResult { @@ -48,4 +54,92 @@ public Fetch generateFetchableFetch( } return fetch; } + + @Override + public CollectionInitializer createInitializer( + InitializerParent parent, + AssemblerCreationState creationState) { + CollectionInitializer initializer = super.createInitializer( parent, creationState ); + return new ReactiveCollectionInitializerAdapter<>( initializer ); + } + + public static class ReactiveCollectionInitializerAdapter + implements CollectionInitializer { + + private final CollectionInitializer delegate; + + public ReactiveCollectionInitializerAdapter(CollectionInitializer initializer) { + this.delegate = initializer; + } + + @Override + public NavigablePath getNavigablePath() { + return delegate.getNavigablePath(); + } + + @Override + public PluralAttributeMapping getInitializedPart() { + return delegate.getInitializedPart(); + } + + @Override + public T getData(RowProcessingState rowProcessingState) { + return delegate.getData( rowProcessingState ); + } + + @Override + public void startLoading(RowProcessingState rowProcessingState) { + delegate.startLoading( rowProcessingState ); + } + + @Override + public void resolveKey(T data) { + delegate.resolveKey( data ); + } + + @Override + public void resolveInstance(T data) { + delegate.resolveInstance( data ); + } + + @Override + public void resolveState(T data) { + + } + + @Override + public void initializeInstance(T data) { + delegate.initializeInstance( data ); + } + + @Override + public void finishUpRow(T data) { + delegate.finishUpRow( data ); + } + + @Override + public boolean isPartOfKey() { + return delegate.isPartOfKey(); + } + + @Override + public boolean isEager() { + return delegate.isEager(); + } + + @Override + public boolean hasLazySubInitializers() { + return delegate.hasLazySubInitializers(); + } + + @Override + public boolean isResultInitializer() { + return delegate.isResultInitializer(); + } + + @Override + public PersistentCollection getCollectionInstance(T data) { + return delegate.getCollectionInstance( data ); + } + } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableFetchImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableFetchImpl.java new file mode 100644 index 000000000..50034533b --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableFetchImpl.java @@ -0,0 +1,40 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.sql.results.graph.embeddable.internal; + +import org.hibernate.engine.FetchTiming; +import org.hibernate.spi.NavigablePath; +import org.hibernate.sql.results.graph.AssemblerCreationState; +import org.hibernate.sql.results.graph.DomainResultCreationState; +import org.hibernate.sql.results.graph.FetchParent; +import org.hibernate.sql.results.graph.InitializerParent; +import org.hibernate.sql.results.graph.embeddable.EmbeddableInitializer; +import org.hibernate.sql.results.graph.embeddable.EmbeddableValuedFetchable; +import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableFetchImpl; + +public class ReactiveEmbeddableFetchImpl extends EmbeddableFetchImpl { + + public ReactiveEmbeddableFetchImpl( + NavigablePath navigablePath, + EmbeddableValuedFetchable embeddedPartDescriptor, + FetchParent fetchParent, + FetchTiming fetchTiming, + boolean hasTableGroup, + DomainResultCreationState creationState) { + super( navigablePath, embeddedPartDescriptor, fetchParent, fetchTiming, hasTableGroup, creationState ); + } + + public ReactiveEmbeddableFetchImpl(EmbeddableFetchImpl original) { + super( original ); + } + + @Override + public EmbeddableInitializer createInitializer( + InitializerParent parent, + AssemblerCreationState creationState) { + return new ReactiveEmbeddableInitializerImpl( this, getDiscriminatorFetch(), parent, creationState, true ); + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableForeignKeyResultImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableForeignKeyResultImpl.java new file mode 100644 index 000000000..a708481cf --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableForeignKeyResultImpl.java @@ -0,0 +1,27 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.sql.results.graph.embeddable.internal; + +import org.hibernate.metamodel.mapping.NonAggregatedIdentifierMapping; +import org.hibernate.sql.results.graph.AssemblerCreationState; +import org.hibernate.sql.results.graph.InitializerParent; +import org.hibernate.sql.results.graph.embeddable.EmbeddableInitializer; +import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableForeignKeyResultImpl; +import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableInitializerImpl; + +public class ReactiveEmbeddableForeignKeyResultImpl extends EmbeddableForeignKeyResultImpl { + + public ReactiveEmbeddableForeignKeyResultImpl(EmbeddableForeignKeyResultImpl original) { + super( original ); + } + + @Override + public EmbeddableInitializer createInitializer(InitializerParent parent, AssemblerCreationState creationState) { + return getReferencedModePart() instanceof NonAggregatedIdentifierMapping + ? new ReactiveNonAggregatedIdentifierMappingInitializer( this, null, creationState, true ) + : new EmbeddableInitializerImpl( this, null, null, creationState, true ); + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableInitializerImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableInitializerImpl.java new file mode 100644 index 000000000..3e760d9b0 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveEmbeddableInitializerImpl.java @@ -0,0 +1,91 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.sql.results.graph.embeddable.internal; + +import java.util.concurrent.CompletionStage; +import java.util.function.BiFunction; + +import org.hibernate.metamodel.mapping.EmbeddableMappingType; +import org.hibernate.reactive.sql.results.graph.ReactiveInitializer; +import org.hibernate.sql.results.graph.AssemblerCreationState; +import org.hibernate.sql.results.graph.Initializer; +import org.hibernate.sql.results.graph.InitializerData; +import org.hibernate.sql.results.graph.InitializerParent; +import org.hibernate.sql.results.graph.basic.BasicFetch; +import org.hibernate.sql.results.graph.embeddable.EmbeddableResultGraphNode; +import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableInitializerImpl; +import org.hibernate.sql.results.jdbc.spi.RowProcessingState; + +import static org.hibernate.reactive.util.impl.CompletionStages.loop; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; + +public class ReactiveEmbeddableInitializerImpl extends EmbeddableInitializerImpl + implements ReactiveInitializer { + + private static class ReactiveEmbeddableInitializerData extends EmbeddableInitializerData { + + public ReactiveEmbeddableInitializerData( + EmbeddableInitializerImpl initializer, + RowProcessingState rowProcessingState) { + super( initializer, rowProcessingState ); + } + + public EmbeddableMappingType.ConcreteEmbeddableType getConcreteEmbeddableType() { + return super.concreteEmbeddableType; + } + } + + public ReactiveEmbeddableInitializerImpl( + EmbeddableResultGraphNode resultDescriptor, + BasicFetch discriminatorFetch, + InitializerParent parent, + AssemblerCreationState creationState, + boolean isResultInitializer) { + super( resultDescriptor, discriminatorFetch, parent, creationState, isResultInitializer ); + } + + @Override + protected InitializerData createInitializerData(RowProcessingState rowProcessingState) { + return new ReactiveEmbeddableInitializerData( this, rowProcessingState ); + } + + @Override + public CompletionStage reactiveResolveInstance(EmbeddableInitializerData data) { + super.resolveInstance( data ); + return voidFuture(); + } + + @Override + public CompletionStage reactiveInitializeInstance(EmbeddableInitializerData data) { + super.initializeInstance( data ); + return voidFuture(); + } + + @Override + public CompletionStage forEachReactiveSubInitializer( + BiFunction, RowProcessingState, CompletionStage> consumer, + InitializerData data) { + final ReactiveEmbeddableInitializerData embeddableInitializerData = (ReactiveEmbeddableInitializerData) data; + final RowProcessingState rowProcessingState = embeddableInitializerData.getRowProcessingState(); + if ( embeddableInitializerData.getConcreteEmbeddableType() == null ) { + return loop( subInitializers, subInitializer -> loop( subInitializer, initializer -> consumer + .apply( (ReactiveInitializer) initializer, rowProcessingState ) + ) ); + } + else { + Initializer[] initializers = subInitializers[embeddableInitializerData.getSubclassId()]; + return loop( 0, initializers.length, i -> { + ReactiveInitializer reactiveInitializer = (ReactiveInitializer) initializers[i]; + return consumer.apply( reactiveInitializer, rowProcessingState ); + } ); + } + } + + @Override + public Object getResolvedInstance(EmbeddableInitializerData data) { + return super.getResolvedInstance( data ); + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveNonAggregatedIdentifierMappingFetch.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveNonAggregatedIdentifierMappingFetch.java new file mode 100644 index 000000000..2af264526 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveNonAggregatedIdentifierMappingFetch.java @@ -0,0 +1,69 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.sql.results.graph.embeddable.internal; + +import org.hibernate.engine.FetchTiming; +import org.hibernate.metamodel.mapping.NonAggregatedIdentifierMapping; +import org.hibernate.reactive.sql.results.graph.entity.internal.ReactiveEntityFetchJoinedImpl; +import org.hibernate.spi.NavigablePath; +import org.hibernate.sql.results.graph.AssemblerCreationState; +import org.hibernate.sql.results.graph.DomainResultCreationState; +import org.hibernate.sql.results.graph.Fetch; +import org.hibernate.sql.results.graph.FetchParent; +import org.hibernate.sql.results.graph.Fetchable; +import org.hibernate.sql.results.graph.InitializerParent; +import org.hibernate.sql.results.graph.embeddable.EmbeddableInitializer; +import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableFetchImpl; +import org.hibernate.sql.results.graph.embeddable.internal.NonAggregatedIdentifierMappingFetch; +import org.hibernate.sql.results.graph.entity.internal.EntityFetchJoinedImpl; + +public class ReactiveNonAggregatedIdentifierMappingFetch extends ReactiveEmbeddableFetchImpl { + public ReactiveNonAggregatedIdentifierMappingFetch( + NavigablePath navigablePath, + NonAggregatedIdentifierMapping embeddedPartDescriptor, + FetchParent fetchParent, + FetchTiming fetchTiming, + boolean hasTableGroup, + DomainResultCreationState creationState) { + super( navigablePath, embeddedPartDescriptor, fetchParent, fetchTiming, hasTableGroup, creationState ); + } + + public ReactiveNonAggregatedIdentifierMappingFetch(NonAggregatedIdentifierMappingFetch fetch) { + super( fetch ); + } + + @Override + public EmbeddableInitializer createInitializer( + InitializerParent parent, + AssemblerCreationState creationState) { + return new ReactiveNonAggregatedIdentifierMappingInitializer( this, parent, creationState, false ); + } + + @Override + public Fetch generateFetchableFetch( + Fetchable fetchable, + NavigablePath fetchablePath, + FetchTiming fetchTiming, + boolean selected, + String resultVariable, + DomainResultCreationState creationState) { + Fetch fetch = super.generateFetchableFetch( + fetchable, + fetchablePath, + fetchTiming, + selected, + resultVariable, + creationState + ); + if ( fetch instanceof EmbeddableFetchImpl ) { + return new ReactiveEmbeddableFetchImpl( (EmbeddableFetchImpl) fetch ); + } + if ( fetch instanceof EntityFetchJoinedImpl ) { + return new ReactiveEntityFetchJoinedImpl( (EntityFetchJoinedImpl) fetch ); + } + return fetch; + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveNonAggregatedIdentifierMappingInitializer.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveNonAggregatedIdentifierMappingInitializer.java new file mode 100644 index 000000000..0096e8d0f --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/embeddable/internal/ReactiveNonAggregatedIdentifierMappingInitializer.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.sql.results.graph.embeddable.internal; + +import java.util.concurrent.CompletionStage; +import java.util.function.BiFunction; + +import org.hibernate.reactive.sql.results.graph.ReactiveInitializer; +import org.hibernate.reactive.sql.results.graph.entity.internal.ReactiveEntityFetchJoinedImpl; +import org.hibernate.sql.results.graph.AssemblerCreationState; +import org.hibernate.sql.results.graph.Fetch; +import org.hibernate.sql.results.graph.InitializerData; +import org.hibernate.sql.results.graph.InitializerParent; +import org.hibernate.sql.results.graph.embeddable.EmbeddableResultGraphNode; +import org.hibernate.sql.results.graph.embeddable.internal.NonAggregatedIdentifierMappingInitializer; +import org.hibernate.sql.results.graph.entity.internal.EntityFetchJoinedImpl; +import org.hibernate.sql.results.jdbc.spi.RowProcessingState; + +import static org.hibernate.reactive.util.impl.CompletionStages.loop; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; + +public class ReactiveNonAggregatedIdentifierMappingInitializer extends NonAggregatedIdentifierMappingInitializer + implements ReactiveInitializer { + + public ReactiveNonAggregatedIdentifierMappingInitializer( + EmbeddableResultGraphNode resultDescriptor, + InitializerParent parent, + AssemblerCreationState creationState, + boolean isResultInitializer) { + super( + resultDescriptor, + parent, + creationState, + isResultInitializer, + ReactiveNonAggregatedIdentifierMappingInitializer::convertFetch + ); + } + + private static Fetch convertFetch(Fetch fetch) { + if ( fetch instanceof EntityFetchJoinedImpl ) { + return new ReactiveEntityFetchJoinedImpl( (EntityFetchJoinedImpl) fetch ); + } + return fetch; + } + + @Override + public CompletionStage reactiveResolveKey(NonAggregatedIdentifierMappingInitializerData data) { + if ( data.getState() != State.UNINITIALIZED ) { + return voidFuture(); + } + // We need to possibly wrap the processing state if the embeddable is within an aggregate + data.setInstance( null ); + data.setState( State.KEY_RESOLVED ); + if ( getInitializers().length == 0 ) { + // Resolve the component early to know if the key is missing or not + return reactiveResolveInstance( data ); + } + else { + final RowProcessingState rowProcessingState = data.getRowProcessingState(); + final boolean[] dataIsMissing = {false}; + return loop( getInitializers(), initializer -> { + if ( dataIsMissing[0] ) { + return voidFuture(); + } + final InitializerData subData = ( (ReactiveInitializer) initializer ) + .getData( rowProcessingState ); + return ( (ReactiveInitializer) initializer ) + .reactiveResolveKey( subData ) + .thenAccept( v -> { + if ( subData.getState() == State.MISSING ) { + data.setState( State.MISSING ); + dataIsMissing[0] = true; + } + } ); + } ); + } + } + + @Override + public CompletionStage reactiveResolveInstance(NonAggregatedIdentifierMappingInitializerData data) { + super.resolveInstance( data ); + return voidFuture(); + } + + @Override + public CompletionStage reactiveInitializeInstance(NonAggregatedIdentifierMappingInitializerData data) { + super.initializeInstance( data ); + return voidFuture(); + } + + @Override + public CompletionStage forEachReactiveSubInitializer( + BiFunction, RowProcessingState, CompletionStage> consumer, + InitializerData data) { + final RowProcessingState rowProcessingState = data.getRowProcessingState(); + return loop( getInitializers(), initializer -> consumer + .apply( (ReactiveInitializer) initializer, rowProcessingState ) + ); + } + + @Override + public Object getResolvedInstance(NonAggregatedIdentifierMappingInitializerData data) { + return super.getResolvedInstance( data ); + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/ReactiveAbstractEntityInitializer.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/ReactiveAbstractEntityInitializer.java deleted file mode 100644 index 82aaa435d..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/ReactiveAbstractEntityInitializer.java +++ /dev/null @@ -1,219 +0,0 @@ -/* Hibernate, Relational Persistence for Idiomatic Java - * - * SPDX-License-Identifier: Apache-2.0 - * Copyright: Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.reactive.sql.results.graph.entity; - -import java.lang.invoke.MethodHandles; -import java.util.concurrent.CompletionStage; - -import org.hibernate.LockMode; -import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; -import org.hibernate.engine.spi.EntityEntry; -import org.hibernate.engine.spi.PersistenceContext; -import org.hibernate.engine.spi.PersistentAttributeInterceptor; -import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.engine.spi.Status; -import org.hibernate.reactive.logging.impl.Log; -import org.hibernate.reactive.logging.impl.LoggerFactory; -import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; -import org.hibernate.reactive.sql.results.graph.ReactiveDomainResultsAssembler; -import org.hibernate.reactive.sql.results.graph.ReactiveInitializer; -import org.hibernate.spi.NavigablePath; -import org.hibernate.sql.results.graph.AssemblerCreationState; -import org.hibernate.sql.results.graph.DomainResult; -import org.hibernate.sql.results.graph.DomainResultAssembler; -import org.hibernate.sql.results.graph.Fetch; -import org.hibernate.sql.results.graph.FetchParentAccess; -import org.hibernate.sql.results.graph.entity.AbstractEntityInitializer; -import org.hibernate.sql.results.graph.entity.EntityLoadingLogging; -import org.hibernate.sql.results.graph.entity.EntityResultGraphNode; -import org.hibernate.sql.results.jdbc.spi.RowProcessingState; -import org.hibernate.stat.spi.StatisticsImplementor; - -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.internal.log.LoggingHelper.toLoggableString; -import static org.hibernate.reactive.util.impl.CompletionStages.loop; -import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; - -public abstract class ReactiveAbstractEntityInitializer extends AbstractEntityInitializer implements ReactiveInitializer { - - private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); - - protected ReactiveAbstractEntityInitializer( - EntityResultGraphNode resultDescriptor, - NavigablePath navigablePath, - LockMode lockMode, - Fetch identifierFetch, - Fetch discriminatorFetch, - DomainResult rowIdResult, - AssemblerCreationState creationState) { - this( - resultDescriptor, - navigablePath, - lockMode, - identifierFetch, - discriminatorFetch, - rowIdResult, - null, - creationState - ); - } - - protected ReactiveAbstractEntityInitializer( - EntityResultGraphNode resultDescriptor, - NavigablePath navigablePath, - LockMode lockMode, - Fetch identifierFetch, - Fetch discriminatorFetch, - DomainResult rowIdResult, - FetchParentAccess parentAccess, - AssemblerCreationState creationState) { - super( - resultDescriptor, - navigablePath, - lockMode, - identifierFetch, - discriminatorFetch, - rowIdResult, - parentAccess, - creationState - ); - } - - @Override - public void resolveInstance(RowProcessingState rowProcessingState) { - super.resolveInstance( rowProcessingState ); - } - - @Override - public void initializeInstance(RowProcessingState rowProcessingState) { - throw LOG.nonReactiveMethodCall( "reactiveInitializeInstance" ); - } - - @Override - public CompletionStage reactiveResolveInstance(ReactiveRowProcessingState rowProcessingState) { - super.resolveInstance( rowProcessingState ); - return voidFuture(); - } - - @Override - public CompletionStage reactiveInitializeInstance(ReactiveRowProcessingState rowProcessingState) { - if ( state == State.KEY_RESOLVED || state == State.RESOLVED ) { - return initializeEntity( getEntityInstanceForNotify(), rowProcessingState ) - .thenAccept( v -> state = State.INITIALIZED ); - } - return voidFuture(); - } - - protected CompletionStage initializeEntity(Object toInitialize, RowProcessingState rowProcessingState) { - if ( !skipInitialization( toInitialize, rowProcessingState ) ) { - assert consistentInstance( toInitialize, rowProcessingState ); - return initializeEntityInstance( toInitialize, rowProcessingState ); - } - return voidFuture(); - } - - - protected CompletionStage reactiveExtractConcreteTypeStateValues(RowProcessingState rowProcessingState) { - final Object[] values = new Object[getConcreteDescriptor().getNumberOfAttributeMappings()]; - final DomainResultAssembler[] concreteAssemblers = getAssemblers()[getConcreteDescriptor().getSubclassId()]; - return loop( 0, values.length, i -> { - final DomainResultAssembler assembler = concreteAssemblers[i]; - if ( assembler instanceof ReactiveDomainResultsAssembler) { - return ( (ReactiveDomainResultsAssembler) assembler ) - .reactiveAssemble( (ReactiveRowProcessingState) rowProcessingState ) - .thenAccept( obj -> values[i] = obj ); - } - else { - values[i] = assembler == null ? UNFETCHED_PROPERTY : assembler.assemble( rowProcessingState ); - return voidFuture(); - } - } ).thenApply( unused -> values ); - } - - private CompletionStage initializeEntityInstance(Object toInitialize, RowProcessingState rowProcessingState) { - final Object entityIdentifier = getEntityKey().getIdentifier(); - final SharedSessionContractImplementor session = rowProcessingState.getSession(); - final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); - - if ( EntityLoadingLogging.ENTITY_LOADING_LOGGER.isTraceEnabled() ) { - EntityLoadingLogging.ENTITY_LOADING_LOGGER.tracef( - "(%s) Beginning Initializer#initializeInstance process for entity %s", - getSimpleConcreteImplName(), - toLoggableString( getNavigablePath(), entityIdentifier ) - ); - } - - getEntityDescriptor().setIdentifier( toInitialize, entityIdentifier, session ); - return reactiveExtractConcreteTypeStateValues( rowProcessingState ) - .thenCompose( entityState -> loop( 0, entityState.length, i -> { - if ( entityState[i] instanceof CompletionStage ) { - return ( (CompletionStage) entityState[i] ) - .thenAccept( state -> entityState[i] = state ); - } - return voidFuture(); - } ).thenAccept( v -> setResolvedEntityState( entityState ) ) - ) - .thenAccept( v -> { - if ( isPersistentAttributeInterceptable(toInitialize) ) { - PersistentAttributeInterceptor persistentAttributeInterceptor = - asPersistentAttributeInterceptable( toInitialize ).$$_hibernate_getInterceptor(); - if ( persistentAttributeInterceptor == null - || persistentAttributeInterceptor instanceof EnhancementAsProxyLazinessInterceptor ) { - // if we do this after the entity has been initialized the - // BytecodeLazyAttributeInterceptor#isAttributeLoaded(String fieldName) would return false; - getConcreteDescriptor().getBytecodeEnhancementMetadata() - .injectInterceptor( toInitialize, entityIdentifier, session ); - } - } - getConcreteDescriptor().setValues( toInitialize, getResolvedEntityState() ); - persistenceContext.addEntity( getEntityKey(), toInitialize ); - - // Also register possible unique key entries - registerPossibleUniqueKeyEntries( toInitialize, session ); - - final Object version = getVersionAssembler() != null ? getVersionAssembler().assemble( rowProcessingState ) : null; - final Object rowId = getRowIdAssembler() != null ? getRowIdAssembler().assemble( rowProcessingState ) : null; - - // from the perspective of Hibernate, an entity is read locked as soon as it is read - // so regardless of the requested lock mode, we upgrade to at least the read level - final LockMode lockModeToAcquire = getLockMode() == LockMode.NONE ? LockMode.READ : getLockMode(); - - final EntityEntry entityEntry = persistenceContext.addEntry( - toInitialize, - Status.LOADING, - getResolvedEntityState(), - rowId, - getEntityKey().getIdentifier(), - version, - lockModeToAcquire, - true, - getConcreteDescriptor(), - false - ); - - updateCaches( toInitialize, rowProcessingState, session, persistenceContext, entityIdentifier, version ); - registerNaturalIdResolution( persistenceContext, entityIdentifier ); - takeSnapshot( rowProcessingState, session, persistenceContext, entityEntry ); - getConcreteDescriptor().afterInitialize( toInitialize, session ); - if ( EntityLoadingLogging.ENTITY_LOADING_LOGGER.isDebugEnabled() ) { - EntityLoadingLogging.ENTITY_LOADING_LOGGER.debugf( - "(%s) Done materializing entityInstance : %s", - getSimpleConcreteImplName(), - toLoggableString( getNavigablePath(), entityIdentifier ) - ); - } - - final StatisticsImplementor statistics = session.getFactory().getStatistics(); - if ( statistics.isStatisticsEnabled() ) { - if ( !rowProcessingState.isQueryCacheHit() ) { - statistics.loadEntity( getConcreteDescriptor().getEntityName() ); - } - } - } ); - } -} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityAssembler.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityAssembler.java index be90edbb6..eb57f92fa 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityAssembler.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityAssembler.java @@ -5,21 +5,19 @@ */ package org.hibernate.reactive.sql.results.graph.entity.internal; - import java.util.concurrent.CompletionStage; -import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; import org.hibernate.reactive.sql.results.graph.ReactiveDomainResultsAssembler; import org.hibernate.reactive.sql.results.graph.ReactiveInitializer; +import org.hibernate.sql.results.graph.Initializer; +import org.hibernate.sql.results.graph.InitializerData; import org.hibernate.sql.results.graph.entity.EntityInitializer; import org.hibernate.sql.results.graph.entity.internal.EntityAssembler; import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions; -import org.hibernate.sql.results.jdbc.spi.RowProcessingState; import org.hibernate.type.descriptor.java.JavaType; -import static java.lang.invoke.MethodHandles.lookup; -import static org.hibernate.reactive.logging.impl.LoggerFactory.make; +import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; /** * @see org.hibernate.sql.results.graph.entity.internal.EntityAssembler @@ -27,19 +25,7 @@ public class ReactiveEntityAssembler extends EntityAssembler implements ReactiveDomainResultsAssembler { public ReactiveEntityAssembler(JavaType javaType, EntityInitializer initializer) { - super(javaType, initializer); - } - - @Override - public Object assemble(RowProcessingState rowProcessingState) { - throw make( Log.class, lookup() ) - .nonReactiveMethodCall( "reactiveAssemble" ); - } - - @Override - public Object assemble(RowProcessingState rowProcessingState, JdbcValuesSourceProcessingOptions options) { - throw make( Log.class, lookup() ) - .nonReactiveMethodCall( "reactiveAssemble" ); + super( javaType, initializer ); } @Override @@ -47,9 +33,14 @@ public CompletionStage reactiveAssemble(ReactiveRowProcessingState rowPr // Ensure that the instance really is initialized // This is important for key-many-to-ones that are part of a collection key fk, // as the instance is needed for resolveKey before initializing the instance in RowReader - return ( (ReactiveInitializer) getInitializer() ) - .reactiveResolveInstance( rowProcessingState ) - .thenApply( v -> getInitializer().getEntityInstance() ); - + final ReactiveInitializer reactiveInitializer = (ReactiveInitializer) getInitializer(); + final InitializerData data = reactiveInitializer.getData( rowProcessingState ); + final Initializer.State state = data.getState(); + if ( state == Initializer.State.KEY_RESOLVED ) { + return reactiveInitializer + .reactiveResolveInstance( data ) + .thenApply( v -> reactiveInitializer.getResolvedInstance( data ) ); + } + return completedFuture( reactiveInitializer.getResolvedInstance( data ) ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityDelayedFetchInitializer.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityDelayedFetchInitializer.java index e761ce183..1a1660725 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityDelayedFetchInitializer.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityDelayedFetchInitializer.java @@ -6,7 +6,9 @@ package org.hibernate.reactive.sql.results.graph.entity.internal; import java.util.concurrent.CompletionStage; +import java.util.function.BiFunction; +import org.hibernate.FetchNotFoundException; import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; import org.hibernate.engine.spi.EntityHolder; import org.hibernate.engine.spi.EntityKey; @@ -19,53 +21,111 @@ import org.hibernate.proxy.LazyInitializer; import org.hibernate.reactive.persister.entity.impl.ReactiveEntityPersister; import org.hibernate.reactive.session.impl.ReactiveQueryExecutorLookup; -import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; import org.hibernate.reactive.sql.results.graph.ReactiveInitializer; +import org.hibernate.reactive.sql.results.graph.embeddable.internal.ReactiveEmbeddableForeignKeyResultImpl; import org.hibernate.spi.NavigablePath; -import org.hibernate.sql.results.graph.DomainResultAssembler; -import org.hibernate.sql.results.graph.FetchParentAccess; +import org.hibernate.sql.results.graph.AssemblerCreationState; +import org.hibernate.sql.results.graph.DomainResult; +import org.hibernate.sql.results.graph.InitializerData; +import org.hibernate.sql.results.graph.InitializerParent; +import org.hibernate.sql.results.graph.basic.BasicFetch; +import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableForeignKeyResultImpl; import org.hibernate.sql.results.graph.entity.internal.EntityDelayedFetchInitializer; +import org.hibernate.sql.results.jdbc.spi.RowProcessingState; import org.hibernate.type.Type; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; +import static org.hibernate.sql.results.graph.entity.internal.EntityInitializerImpl.determineConcreteEntityDescriptor; -public class ReactiveEntityDelayedFetchInitializer extends EntityDelayedFetchInitializer implements ReactiveInitializer { +public class ReactiveEntityDelayedFetchInitializer extends EntityDelayedFetchInitializer + implements ReactiveInitializer { private final ToOneAttributeMapping referencedModelPart; public ReactiveEntityDelayedFetchInitializer( - FetchParentAccess parentAccess, + InitializerParent parent, NavigablePath fetchedNavigable, ToOneAttributeMapping referencedModelPart, boolean selectByUniqueKey, - DomainResultAssembler identifierAssembler) { - super( parentAccess, fetchedNavigable, referencedModelPart, selectByUniqueKey, identifierAssembler ); + DomainResult keyResult, + BasicFetch discriminatorResult, + AssemblerCreationState creationState) { + super( + parent, + fetchedNavigable, + referencedModelPart, + selectByUniqueKey, + convert(keyResult), + discriminatorResult, + creationState + ); this.referencedModelPart = referencedModelPart; } - @Override - public CompletionStage reactiveResolveInstance(ReactiveRowProcessingState rowProcessingState) { - if ( isProcessed() ) { - return voidFuture(); + private static DomainResult convert(DomainResult keyResult) { + return keyResult instanceof EmbeddableForeignKeyResultImpl + ? new ReactiveEmbeddableForeignKeyResultImpl<>( (EmbeddableForeignKeyResultImpl) keyResult ) + : keyResult; + } + + public static class ReactiveEntityDelayedFetchInitializerData extends EntityDelayedFetchInitializerData { + + public ReactiveEntityDelayedFetchInitializerData(RowProcessingState rowProcessingState) { + super( rowProcessingState ); } - setProcessed( true ); + public Object getEntityIdentifier() { + return entityIdentifier; + } - // We can avoid processing further if the parent is already initialized or missing, - // as the value produced by this initializer will never be used anyway. - if ( parentShallowCached || shouldSkipInitializer( rowProcessingState ) ) { + public void setEntityIdentifier(Object entityIdentifier) { + this.entityIdentifier = entityIdentifier; + } + } + + @Override + protected InitializerData createInitializerData(RowProcessingState rowProcessingState) { + return new ReactiveEntityDelayedFetchInitializerData( rowProcessingState ); + } + + @Override + public CompletionStage reactiveResolveInstance(EntityDelayedFetchInitializerData initializerData) { + if ( initializerData.getState() != State.KEY_RESOLVED ) { return voidFuture(); } - setIdentifier( getIdentifierAssembler().assemble( rowProcessingState ) ); + ReactiveEntityDelayedFetchInitializerData data = (ReactiveEntityDelayedFetchInitializerData) initializerData; + data.setState( State.RESOLVED ); + + final RowProcessingState rowProcessingState = data.getRowProcessingState(); + data.setEntityIdentifier( getIdentifierAssembler().assemble( rowProcessingState ) ); CompletionStage stage = voidFuture(); - if ( getIdentifier() == null ) { - setEntityInstance( null ); + if ( data.getEntityIdentifier() == null ) { + data.setInstance( null ); + data.setState( State.MISSING ); } else { final SharedSessionContractImplementor session = rowProcessingState.getSession(); - final EntityPersister concreteDescriptor = referencedModelPart.getEntityMappingType().getEntityPersister(); + + final EntityPersister entityPersister = referencedModelPart.getEntityMappingType().getEntityPersister(); + final EntityPersister concreteDescriptor; + if ( getDiscriminatorAssembler() != null ) { + concreteDescriptor = determineConcreteEntityDescriptor( rowProcessingState, getDiscriminatorAssembler(), entityPersister ); + if ( concreteDescriptor == null ) { + // If we find no discriminator it means there's no entity in the target table + if ( !referencedModelPart.isOptional() ) { + throw new FetchNotFoundException( entityPersister.getEntityName(), data.getEntityIdentifier() ); + } + data.setInstance( null ); + data.setState( State.MISSING ); + return voidFuture(); + } + } + else { + concreteDescriptor = entityPersister; + } + final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); if ( isSelectByUniqueKey() ) { final String uniqueKeyPropertyName = referencedModelPart.getReferencedPropertyName(); @@ -75,58 +135,67 @@ public CompletionStage reactiveResolveInstance(ReactiveRowProcessingState final EntityUniqueKey euk = new EntityUniqueKey( concreteDescriptor.getEntityName(), uniqueKeyPropertyName, - getIdentifier(), + data.getEntityIdentifier(), uniqueKeyPropertyType, session.getFactory() ); - setEntityInstance( persistenceContext.getEntity( euk ) ); - if ( getEntityInstance() == null ) { + data.setInstance( persistenceContext.getEntity( euk ) ); + if ( data.getInstance() == null ) { // For unique-key mappings, we always use bytecode-laziness if possible, // because we can't generate a proxy based on the unique key yet if ( referencedModelPart.isLazy() ) { - setEntityInstance( LazyPropertyInitializer.UNFETCHED_PROPERTY ); + data.setInstance( LazyPropertyInitializer.UNFETCHED_PROPERTY ); } else { stage = stage .thenCompose( v -> ( (ReactiveEntityPersister) concreteDescriptor ) - .reactiveLoadByUniqueKey( uniqueKeyPropertyName, getIdentifier(), session ) ) - .thenAccept( this::setEntityInstance ) + .reactiveLoadByUniqueKey( + uniqueKeyPropertyName, + data.getEntityIdentifier(), + session + ) ) + .thenAccept( data::setInstance ) .thenAccept( v -> { // If the entity was not in the Persistence Context, but was found now, // add it to the Persistence Context - if ( getEntityInstance() != null ) { - persistenceContext.addEntity( euk, getEntityInstance() ); + if ( data.getInstance() != null ) { + persistenceContext.addEntity( euk, data.getInstance() ); } } ); } } stage = stage.thenAccept( v -> { - if ( getEntityInstance() != null ) { - setEntityInstance( persistenceContext.proxyFor( getEntityInstance() ) ); + if ( data.getInstance() != null ) { + data.setInstance( persistenceContext.proxyFor( data.getInstance() ) ); } } ); } else { - final EntityKey entityKey = new EntityKey( getIdentifier(), concreteDescriptor ); + final EntityKey entityKey = new EntityKey( data.getEntityIdentifier(), concreteDescriptor ); final EntityHolder holder = persistenceContext.getEntityHolder( entityKey ); if ( holder != null && holder.getEntity() != null ) { - setEntityInstance( persistenceContext.proxyFor( holder, concreteDescriptor ) ); + data.setInstance( persistenceContext.proxyFor( holder, concreteDescriptor ) ); } // For primary key based mappings we only use bytecode-laziness if the attribute is optional, // because the non-optionality implies that it is safe to have a proxy else if ( referencedModelPart.isOptional() && referencedModelPart.isLazy() ) { - setEntityInstance( LazyPropertyInitializer.UNFETCHED_PROPERTY ); + data.setInstance( LazyPropertyInitializer.UNFETCHED_PROPERTY ); } else { stage = stage.thenCompose( v -> ReactiveQueryExecutorLookup .extract( session ) - .reactiveInternalLoad( concreteDescriptor.getEntityName(), getIdentifier(), false, false ) - .thenAccept( this::setEntityInstance ) + .reactiveInternalLoad( + concreteDescriptor.getEntityName(), + data.getEntityIdentifier(), + false, + false + ) + .thenAccept( data::setInstance ) ); } stage = stage .thenAccept( v -> { - final LazyInitializer lazyInitializer = HibernateProxy.extractLazyInitializer( getEntityInstance() ); + final LazyInitializer lazyInitializer = HibernateProxy.extractLazyInitializer( data.getInstance() ); if ( lazyInitializer != null ) { lazyInitializer.setUnwrap( referencedModelPart.isUnwrapProxy() && concreteDescriptor.isInstrumented() ); } @@ -137,7 +206,24 @@ else if ( referencedModelPart.isOptional() && referencedModelPart.isLazy() ) { } @Override - public CompletionStage reactiveInitializeInstance(ReactiveRowProcessingState rowProcessingState) { + public CompletionStage reactiveInitializeInstance(EntityDelayedFetchInitializerData data) { + // No-op by default + return voidFuture(); + } + + @Override + public CompletionStage forEachReactiveSubInitializer( + BiFunction, RowProcessingState, CompletionStage> consumer, + InitializerData data) { + final ReactiveInitializer initializer = (ReactiveInitializer) getIdentifierAssembler().getInitializer(); + if ( initializer != null ) { + return consumer.apply( initializer, data.getRowProcessingState() ); + } return voidFuture(); } + + @Override + public Object getResolvedInstance(EntityDelayedFetchInitializerData data) { + return super.getResolvedInstance( data ); + } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityFetchJoinedImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityFetchJoinedImpl.java index ffc2f047c..7bf7bfb5f 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityFetchJoinedImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityFetchJoinedImpl.java @@ -5,9 +5,19 @@ */ package org.hibernate.reactive.sql.results.graph.entity.internal; +import org.hibernate.engine.FetchTiming; +import org.hibernate.reactive.sql.results.graph.embeddable.internal.ReactiveEmbeddableFetchImpl; +import org.hibernate.reactive.sql.results.graph.embeddable.internal.ReactiveNonAggregatedIdentifierMappingFetch; +import org.hibernate.spi.NavigablePath; import org.hibernate.sql.results.graph.AssemblerCreationState; -import org.hibernate.sql.results.graph.FetchParentAccess; +import org.hibernate.sql.results.graph.DomainResultCreationState; +import org.hibernate.sql.results.graph.Fetch; +import org.hibernate.sql.results.graph.Fetchable; +import org.hibernate.sql.results.graph.InitializerParent; +import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableFetchImpl; +import org.hibernate.sql.results.graph.embeddable.internal.NonAggregatedIdentifierMappingFetch; import org.hibernate.sql.results.graph.entity.EntityInitializer; +import org.hibernate.sql.results.graph.entity.internal.EntityAssembler; import org.hibernate.sql.results.graph.entity.internal.EntityFetchJoinedImpl; public class ReactiveEntityFetchJoinedImpl extends EntityFetchJoinedImpl { @@ -16,19 +26,57 @@ public ReactiveEntityFetchJoinedImpl(EntityFetchJoinedImpl entityFetch) { } @Override - public EntityInitializer createInitializer(FetchParentAccess parentAccess, AssemblerCreationState creationState) { - return new ReactiveEntityJoinedFetchInitializer( - getEntityResult(), - getReferencedModePart(), - getNavigablePath(), - creationState.determineEffectiveLockMode( getSourceAlias() ), - getNotFoundAction(), + public EntityInitializer createInitializer(InitializerParent parent, AssemblerCreationState creationState) { + Fetch identifierFetch = convert( getEntityResult().getIdentifierFetch() ); + return new ReactiveEntityInitializerImpl( + this, + getSourceAlias(), + identifierFetch, + getEntityResult().getDiscriminatorFetch(), getKeyResult(), getEntityResult().getRowIdResult(), - getEntityResult().getIdentifierFetch(), - getEntityResult().getDiscriminatorFetch(), - parentAccess, + getNotFoundAction(), + isAffectedByFilter(), + parent, + false, + creationState + ); + } + + private static Fetch convert(Fetch fetch) { + if ( fetch instanceof ReactiveEmbeddableFetchImpl ) { + return fetch; + } + if ( fetch instanceof EmbeddableFetchImpl ) { + return new ReactiveEmbeddableFetchImpl( (EmbeddableFetchImpl) fetch ); + } + return fetch; + } + + @Override + protected EntityAssembler buildEntityAssembler(EntityInitializer entityInitializer) { + return new ReactiveEntityAssembler( getFetchedMapping().getJavaType(), entityInitializer ); + } + + @Override + public Fetch generateFetchableFetch( + Fetchable fetchable, + NavigablePath fetchablePath, + FetchTiming fetchTiming, + boolean selected, + String resultVariable, + DomainResultCreationState creationState) { + Fetch fetch = super.generateFetchableFetch( + fetchable, + fetchablePath, + fetchTiming, + selected, + resultVariable, creationState ); + if ( fetch instanceof NonAggregatedIdentifierMappingFetch ) { + return new ReactiveNonAggregatedIdentifierMappingFetch( (NonAggregatedIdentifierMappingFetch) fetch ); + } + return fetch; } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityFetchSelectImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityFetchSelectImpl.java index 7caff3460..0cae0a79d 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityFetchSelectImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityFetchSelectImpl.java @@ -6,7 +6,7 @@ package org.hibernate.reactive.sql.results.graph.entity.internal; import org.hibernate.sql.results.graph.AssemblerCreationState; -import org.hibernate.sql.results.graph.FetchParentAccess; +import org.hibernate.sql.results.graph.InitializerParent; import org.hibernate.sql.results.graph.entity.EntityInitializer; import org.hibernate.sql.results.graph.entity.internal.EntityAssembler; import org.hibernate.sql.results.graph.entity.internal.EntityFetchSelectImpl; @@ -18,14 +18,15 @@ public ReactiveEntityFetchSelectImpl(EntityFetchSelectImpl original) { } @Override - public EntityInitializer createInitializer(FetchParentAccess parentAccess, AssemblerCreationState creationState) { + public EntityInitializer createInitializer(InitializerParent parent, AssemblerCreationState creationState) { return ReactiveEntitySelectFetchInitializerBuilder.createInitializer( - parentAccess, + parent, getFetchedMapping(), getReferencedMappingContainer().getEntityPersister(), getKeyResult(), getNavigablePath(), isSelectByUniqueKey(), + isAffectedByFilter(), creationState ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityInitializerImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityInitializerImpl.java new file mode 100644 index 000000000..a00287eab --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityInitializerImpl.java @@ -0,0 +1,853 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.sql.results.graph.entity.internal; + +import java.util.concurrent.CompletionStage; +import java.util.function.BiFunction; + +import org.hibernate.Hibernate; +import org.hibernate.LockMode; +import org.hibernate.annotations.NotFoundAction; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; +import org.hibernate.engine.spi.EntityEntry; +import org.hibernate.engine.spi.EntityHolder; +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.SharedSessionContractImplementor; +import org.hibernate.engine.spi.Status; +import org.hibernate.loader.ast.internal.CacheEntityLoaderHelper; +import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.proxy.LazyInitializer; +import org.hibernate.proxy.map.MapProxy; +import org.hibernate.reactive.session.ReactiveSession; +import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; +import org.hibernate.reactive.sql.results.graph.ReactiveInitializer; +import org.hibernate.sql.results.graph.AssemblerCreationState; +import org.hibernate.sql.results.graph.DomainResult; +import org.hibernate.sql.results.graph.DomainResultAssembler; +import org.hibernate.sql.results.graph.Fetch; +import org.hibernate.sql.results.graph.Initializer; +import org.hibernate.sql.results.graph.InitializerData; +import org.hibernate.sql.results.graph.InitializerParent; +import org.hibernate.sql.results.graph.entity.EntityResultGraphNode; +import org.hibernate.sql.results.graph.entity.internal.EntityInitializerImpl; +import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions; +import org.hibernate.sql.results.jdbc.spi.RowProcessingState; +import org.hibernate.stat.spi.StatisticsImplementor; +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.metamodel.mapping.ForeignKeyDescriptor.Nature.TARGET; +import static org.hibernate.proxy.HibernateProxy.extractLazyInitializer; +import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; +import static org.hibernate.reactive.util.impl.CompletionStages.falseFuture; +import static org.hibernate.reactive.util.impl.CompletionStages.loop; +import static org.hibernate.reactive.util.impl.CompletionStages.trueFuture; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; + +public class ReactiveEntityInitializerImpl extends EntityInitializerImpl + implements ReactiveInitializer { + + public static class ReactiveEntityInitializerData extends EntityInitializerData { + + public ReactiveEntityInitializerData(EntityInitializerImpl initializer, RowProcessingState rowProcessingState) { + super( initializer, rowProcessingState ); + } + + public void setEntityInstanceForNotify(Object instance) { + super.entityInstanceForNotify = instance; + } + + public Object getEntityInstanceForNotify() { + return super.entityInstanceForNotify; + } + + public EntityPersister getConcreteDescriptor() { + return super.concreteDescriptor; + } + + public void setConcreteDescriptor(EntityPersister entityPersister) { + super.concreteDescriptor = entityPersister; + } + + public EntityHolder getEntityHolder() { + return super.entityHolder; + } + + public void setEntityHolder(EntityHolder entityHolder) { + super.entityHolder = entityHolder; + } + + public EntityKey getEntityKey() { + return super.entityKey; + } + + public void setEntityKey(EntityKey entityKey) { + super.entityKey = entityKey; + } + + public String getUniqueKeyAttributePath() { + return super.uniqueKeyAttributePath; + } + + public Type[] getUniqueKeyPropertyTypes() { + return super.uniqueKeyPropertyTypes; + } + + public boolean getShallowCached() { + return super.shallowCached; + } + + public LockMode getLockMode() { + return super.lockMode; + } + } + + public ReactiveEntityInitializerImpl( + EntityResultGraphNode resultDescriptor, + String sourceAlias, + Fetch identifierFetch, + Fetch discriminatorFetch, + DomainResult keyResult, + DomainResult rowIdResult, + NotFoundAction notFoundAction, + boolean affectedByFilter, + InitializerParent parent, + boolean isResultInitializer, + AssemblerCreationState creationState) { + super( + resultDescriptor, + sourceAlias, + identifierFetch , + discriminatorFetch, + keyResult, + rowIdResult, + notFoundAction, + affectedByFilter, + parent, + isResultInitializer, + creationState + ); + } + + @Override + protected void resolveEntityKey(EntityInitializerData original, Object id) { + ReactiveEntityInitializerData data = (ReactiveEntityInitializerData) original; + if ( data.getConcreteDescriptor() == null ) { + data.setConcreteDescriptor( determineConcreteEntityDescriptor( + data.getRowProcessingState(), + getDiscriminatorAssembler(), + getEntityDescriptor() + ) ); + assert data.getConcreteDescriptor() != null; + } + data.setEntityKey( new EntityKey( id, data.getConcreteDescriptor() ) ); + } + + @Override + public CompletionStage reactiveResolveInstance(Object instance, EntityInitializerData original) { + ReactiveEntityInitializerData data = (ReactiveEntityInitializerData) original; + if ( instance == null ) { + setMissing( data ); + return voidFuture(); + } + data.setInstance( instance ); + final LazyInitializer lazyInitializer = extractLazyInitializer( data.getInstance() ); + final RowProcessingState rowProcessingState = data.getRowProcessingState(); + final SharedSessionContractImplementor session = rowProcessingState.getSession(); + if ( lazyInitializer == null ) { + // Entity is most probably initialized + data.setEntityInstanceForNotify( data.getInstance() ); + data.setConcreteDescriptor( session.getEntityPersister( null, data.getInstance() ) ); + resolveEntityKey( data, data.getConcreteDescriptor().getIdentifier( data.getInstance(), session ) ); + data.setEntityHolder( session.getPersistenceContextInternal().getEntityHolder( data.getEntityKey() ) ); + if ( data.getEntityHolder() == null ) { + // Entity was most probably removed in the same session without setting the reference to null + return reactiveResolveKey( data ) + .thenRun( () -> { + assert data.getState() == State.MISSING; + assert getInitializedPart() instanceof ToOneAttributeMapping + && ( (ToOneAttributeMapping) getInitializedPart() ).getSideNature() == TARGET; + } ); + } + // If the entity initializer is null, we know the entity is fully initialized, + // otherwise it will be initialized by some other initializer + data.setState( data.getEntityHolder().getEntityInitializer() == null ? State.INITIALIZED : State.RESOLVED ); + } + else if ( lazyInitializer.isUninitialized() ) { + data.setState( State.RESOLVED ); + // Read the discriminator from the result set if necessary + EntityPersister persister = getDiscriminatorAssembler() == null + ? getEntityDescriptor() + : determineConcreteEntityDescriptor( rowProcessingState, getDiscriminatorAssembler(), getEntityDescriptor() ); + data.setConcreteDescriptor( persister ); + assert data.getConcreteDescriptor() != null; + resolveEntityKey( data, lazyInitializer.getIdentifier() ); + data.setEntityHolder( session.getPersistenceContextInternal().claimEntityHolderIfPossible( + data.getEntityKey(), + null, + rowProcessingState.getJdbcValuesSourceProcessingState(), + this + ) ); + // Resolve and potentially create the entity instance + data.setEntityInstanceForNotify( resolveEntityInstance( data ) ); + lazyInitializer.setImplementation( data.getEntityInstanceForNotify() ); + registerLoadingEntity( data, data.getEntityInstanceForNotify() ); + } + else { + data.setState( State.INITIALIZED ); + data.setEntityInstanceForNotify( lazyInitializer.getImplementation() ); + data.setConcreteDescriptor( session.getEntityPersister( null, data.getEntityInstanceForNotify() ) ); + resolveEntityKey( data, lazyInitializer.getIdentifier() ); + data.setEntityHolder( session.getPersistenceContextInternal().getEntityHolder( data.getEntityKey() ) ); + } + return reactiveInitializeStage( data, rowProcessingState ) + .thenCompose( v -> { + upgradeLockMode( data ); + if ( data.getState() == State.INITIALIZED ) { + registerReloadedEntity( data ); + resolveInstanceSubInitializers( data ); + if ( rowProcessingState.needsResolveState() ) { + // We need to read result set values to correctly populate the query cache + resolveState( data ); + } + return voidFuture(); + } + else { + return reactiveResolveKeySubInitializers( data ); + } + } ); + } + + private CompletionStage reactiveInitializeStage( + ReactiveEntityInitializerData data, + RowProcessingState rowProcessingState) { + if ( getIdentifierAssembler() != null ) { + final Initializer initializer = getIdentifierAssembler().getInitializer(); + if ( initializer != null ) { + if ( initializer instanceof ReactiveInitializer ) { + return ( (ReactiveInitializer) initializer ) + .reactiveResolveInstance( data.getEntityKey().getIdentifier(), rowProcessingState ); + } + else { + initializer.resolveInstance( data.getEntityKey().getIdentifier(), rowProcessingState ); + } + } + } + return voidFuture(); + } + + @Override + public CompletionStage reactiveResolveInstance(EntityInitializerData original) { + ReactiveEntityInitializerData data = (ReactiveEntityInitializerData) original; + if ( data.getState() != State.KEY_RESOLVED ) { + return voidFuture(); + } + final RowProcessingState rowProcessingState = data.getRowProcessingState(); + data.setState( State.RESOLVED ); + if ( data.getEntityKey() == null ) { + assert getIdentifierAssembler() != null; + final Object id = getIdentifierAssembler().assemble( rowProcessingState ); + if ( id == null ) { + setMissing( data ); + return voidFuture(); + } + resolveEntityKey( data, id ); + } + final PersistenceContext persistenceContext = rowProcessingState.getSession() + .getPersistenceContextInternal(); + data.setEntityHolder( persistenceContext.claimEntityHolderIfPossible( + data.getEntityKey(), + null, + rowProcessingState.getJdbcValuesSourceProcessingState(), + this + ) ); + + if ( useEmbeddedIdentifierInstanceAsEntity( data ) ) { + data.setEntityInstanceForNotify( rowProcessingState.getEntityId() ); + data.setInstance( data.getEntityInstanceForNotify() ); + } + else { + return reactiveResolveEntityInstance1( data ) + .thenAccept( v -> { + if ( data.getUniqueKeyAttributePath() != null ) { + final SharedSessionContractImplementor session = rowProcessingState.getSession(); + final EntityPersister concreteDescriptor = getConcreteDescriptor( data ); + final EntityUniqueKey euk = new EntityUniqueKey( + concreteDescriptor.getEntityName(), + data.getUniqueKeyAttributePath(), + rowProcessingState.getEntityUniqueKey(), + data.getUniqueKeyPropertyTypes()[concreteDescriptor.getSubclassId()], + session.getFactory() + ); + session.getPersistenceContextInternal().addEntity( euk, data.getInstance() ); + } + postResolveInstance( data ); + } ); + } + postResolveInstance( data ); + return voidFuture(); + } + + private void postResolveInstance(ReactiveEntityInitializerData data) { + if ( data.getInstance() != null ) { + upgradeLockMode( data ); + if ( data.getState() == State.INITIALIZED ) { + registerReloadedEntity( data ); + if ( data.getRowProcessingState().needsResolveState() ) { + // We need to read result set values to correctly populate the query cache + resolveState( data ); + } + } + if ( data.getShallowCached() ) { + initializeSubInstancesFromParent( data ); + } + } + } + + @Override + public CompletionStage reactiveInitializeInstance(EntityInitializerData data) { + if ( data.getState() != State.RESOLVED ) { + return voidFuture(); + } + if ( !skipInitialization( data ) ) { + assert consistentInstance( data ); + return reactiveInitializeEntityInstance( (ReactiveEntityInitializerData) data ); + } + data.setState( State.INITIALIZED ); + return voidFuture(); + } + + protected CompletionStage reactiveInitializeEntityInstance(ReactiveEntityInitializerData data) { + final RowProcessingState rowProcessingState = data.getRowProcessingState(); + final Object entityIdentifier = data.getEntityKey().getIdentifier(); + final SharedSessionContractImplementor session = rowProcessingState.getSession(); + final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); + + return reactiveExtractConcreteTypeStateValues( data ) + .thenAccept( resolvedEntityState -> { + + preLoad( data, resolvedEntityState ); + + if ( isPersistentAttributeInterceptable( data.getEntityInstanceForNotify() ) ) { + final PersistentAttributeInterceptor persistentAttributeInterceptor = + asPersistentAttributeInterceptable( data.getEntityInstanceForNotify() ).$$_hibernate_getInterceptor(); + if ( persistentAttributeInterceptor == null + || persistentAttributeInterceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + // if we do this after the entity has been initialized the + // BytecodeLazyAttributeInterceptor#isAttributeLoaded(String fieldName) would return false; + data.getConcreteDescriptor().getBytecodeEnhancementMetadata() + .injectInterceptor( data.getEntityInstanceForNotify(), entityIdentifier, session ); + } + } + data.getConcreteDescriptor().setPropertyValues( data.getEntityInstanceForNotify(), resolvedEntityState ); + + persistenceContext.addEntity( data.getEntityKey(), data.getEntityInstanceForNotify() ); + + // Also register possible unique key entries + registerPossibleUniqueKeyEntries( data, resolvedEntityState, session ); + + final Object version = getVersionAssembler() != null ? getVersionAssembler().assemble( rowProcessingState ) : null; + final Object rowId = getRowIdAssembler() != null ? getRowIdAssembler().assemble( rowProcessingState ) : null; + + // from the perspective of Hibernate, an entity is read locked as soon as it is read + // so regardless of the requested lock mode, we upgrade to at least the read level + final LockMode lockModeToAcquire = data.getLockMode() == LockMode.NONE ? LockMode.READ : data.getLockMode(); + + final EntityEntry entityEntry = persistenceContext.addEntry( + data.getEntityInstanceForNotify(), + Status.LOADING, + resolvedEntityState, + rowId, + data.getEntityKey().getIdentifier(), + version, + lockModeToAcquire, + true, + data.getConcreteDescriptor(), + false + ); + data.getEntityHolder().setEntityEntry( entityEntry ); + + registerNaturalIdResolution( data, persistenceContext, resolvedEntityState ); + + takeSnapshot( data, session, persistenceContext, entityEntry, resolvedEntityState ); + + data.getConcreteDescriptor().afterInitialize( data.getEntityInstanceForNotify(), session ); + + assert data.getConcreteDescriptor().getIdentifier( data.getEntityInstanceForNotify(), session ) != null; + + final StatisticsImplementor statistics = session.getFactory().getStatistics(); + if ( statistics.isStatisticsEnabled() ) { + if ( !rowProcessingState.isQueryCacheHit() ) { + statistics.loadEntity( data.getConcreteDescriptor().getEntityName() ); + } + } + updateCaches( + data, + session, + session.getPersistenceContextInternal(), + resolvedEntityState, + version + ); + } ); + } + + protected CompletionStage reactiveExtractConcreteTypeStateValues(ReactiveEntityInitializerData data) { + final RowProcessingState rowProcessingState = data.getRowProcessingState(); + final Object[] values = new Object[data.getConcreteDescriptor().getNumberOfAttributeMappings()]; + final DomainResultAssembler[] concreteAssemblers = getAssemblers()[data.getConcreteDescriptor().getSubclassId()]; + return loop( 0, values.length, i -> { + final DomainResultAssembler assembler = concreteAssemblers[i]; + if ( assembler instanceof ReactiveEntityAssembler ) { + return ( (ReactiveEntityAssembler) assembler ) + .reactiveAssemble( (ReactiveRowProcessingState) rowProcessingState ) + .thenAccept( assembled -> values[i] = assembled ); + } + values[i] = assembler == null ? UNFETCHED_PROPERTY : assembler.assemble( rowProcessingState ); + return voidFuture(); + } ).thenApply( v -> values ); + } + + protected CompletionStage reactiveResolveEntityInstance1(ReactiveEntityInitializerData data) { + final Object proxy = data.getEntityHolder().getProxy(); + final boolean unwrapProxy = proxy != null && getInitializedPart() instanceof ToOneAttributeMapping + && ( (ToOneAttributeMapping) getInitializedPart() ).isUnwrapProxy() + && getConcreteDescriptor( data ).getBytecodeEnhancementMetadata().isEnhancedForLazyLoading(); + final Object entityFromExecutionContext; + if ( !unwrapProxy && isProxyInstance( proxy ) ) { + if ( ( entityFromExecutionContext = getEntityFromExecutionContext( data ) ) != null ) { + data.setEntityInstanceForNotify( entityFromExecutionContext ); + data.setInstance( data.getEntityInstanceForNotify() ); + // If the entity comes from the execution context, it is treated as not initialized + // so that we can refresh the data as requested + registerReloadedEntity( data ); + } + else { + data.setInstance( proxy ); + if ( Hibernate.isInitialized( data.getInstance() ) ) { + data.setState( State.INITIALIZED ); + data.setEntityInstanceForNotify( Hibernate.unproxy( data.getInstance() ) ); + } + else { + final LazyInitializer lazyInitializer = extractLazyInitializer( data.getInstance() ); + assert lazyInitializer != null; + return reactiveResolveEntityInstance2( data ) + .thenAccept( entityInstance -> { + data.setEntityInstanceForNotify( entityInstance ); + lazyInitializer.setImplementation( data.getEntityInstanceForNotify() ); + ensureEntityIsInitialized( data ); + } ); + } + } + } + else { + final Object existingEntity = data.getEntityHolder().getEntity(); + if ( existingEntity != null ) { + data.setEntityInstanceForNotify( existingEntity ); + data.setInstance( data.getEntityInstanceForNotify() ); + if ( data.getEntityHolder().getEntityInitializer() == null ) { + assert data.getEntityHolder().isInitialized() == isExistingEntityInitialized( existingEntity ); + if ( data.getEntityHolder().isInitialized() ) { + data.setState( State.INITIALIZED ); + } + else if ( isResultInitializer() ) { + registerLoadingEntity( data, data.getInstance() ); + } + } + else if ( data.getEntityHolder().getEntityInitializer() != this ) { + data.setState( State.INITIALIZED ); + } + } + else if ( ( entityFromExecutionContext = getEntityFromExecutionContext( data ) ) != null ) { + // This is the entity to refresh, so don't set the state to initialized + data.setEntityInstanceForNotify( entityFromExecutionContext ); + data.setInstance( data.getEntityInstanceForNotify() ); + if ( isResultInitializer() ) { + registerLoadingEntity( data, data.getInstance() ); + } + } + else { + assert data.getEntityHolder().getEntityInitializer() == this; + // look to see if another initializer from a parent load context or an earlier + // initializer is already loading the entity + return reactiveResolveEntityInstance2( data ) + .thenAccept( entityInstance -> { + data.setEntityInstanceForNotify( entityInstance ); + data.setInstance( data.getEntityInstanceForNotify() ); + final Initializer idInitializer; + if ( data.getEntityHolder().getEntityInitializer() == this && data.getState() != State.INITIALIZED + && getIdentifierAssembler() != null + && ( idInitializer = getIdentifierAssembler().getInitializer() ) != null ) { + // If this is the owning initializer and the returned object is not initialized, + // this means that the entity instance was just instantiated. + // In this case, we want to call "assemble" and hence "initializeInstance" on the initializer + // for possibly non-aggregated identifier mappings, so inject the virtual id representation + idInitializer.initializeInstance( data.getRowProcessingState() ); + } + ensureEntityIsInitialized( data ); + } ); + } + } + ensureEntityIsInitialized( data ); + return voidFuture(); + } + + private void ensureEntityIsInitialized(ReactiveEntityInitializerData data) { + // todo: ensure we initialize the entity + assert !data.getShallowCached() || data.getState() == State.INITIALIZED : "Forgot to initialize the entity"; + } + + protected CompletionStage reactiveResolveEntityInstance2(ReactiveEntityInitializerData data) { + if ( data.getEntityHolder().getEntityInitializer() == this ) { + assert data.getEntityHolder().getEntity() == null; + return reactiveResolveEntityInstance( data ); + } + else { + // the entity is already being loaded elsewhere + return completedFuture( data.getEntityHolder().getEntity() ); + } + } + + protected CompletionStage reactiveResolveEntityInstance(ReactiveEntityInitializerData data) { + final RowProcessingState rowProcessingState = data.getRowProcessingState(); + final Object resolved = resolveToOptionalInstance( data ); + if ( resolved != null ) { + registerLoadingEntity( data, resolved ); + return completedFuture( resolved ); + } + else { + if ( rowProcessingState.isQueryCacheHit() && getEntityDescriptor().useShallowQueryCacheLayout() ) { + // We must load the entity this way, because the query cache entry contains only the primary key + data.setState( State.INITIALIZED ); + final SharedSessionContractImplementor session = rowProcessingState.getSession(); + assert data.getEntityHolder().getEntityInitializer() == this; + // If this initializer owns the entity, we have to remove the entity holder, + // because the subsequent loading process will claim the entity + session.getPersistenceContextInternal().removeEntityHolder( data.getEntityKey() ); + return ( (ReactiveSession) session ).reactiveInternalLoad( + data.getConcreteDescriptor().getEntityName(), + data.getEntityKey().getIdentifier(), + true, + false + ); + } + // We have to query the second level cache if reference cache entries are used + else if ( getEntityDescriptor().canUseReferenceCacheEntries() ) { + final Object cached = resolveInstanceFromCache( data ); + if ( cached != null ) { + // EARLY EXIT!!! + // because the second level cache has reference cache entries, the entity is initialized + data.setState( State.INITIALIZED ); + return completedFuture( cached ); + } + } + final Object instance = instantiateEntity( data ); + registerLoadingEntity( data, instance ); + return completedFuture( instance ); + } + } + + // FIXME: I could change the scope of this method in ORM + private Object resolveToOptionalInstance(ReactiveEntityInitializerData data) { + if ( isResultInitializer() ) { + // this isEntityReturn bit is just for entity loaders, not hql/criteria + final JdbcValuesSourceProcessingOptions processingOptions = + data.getRowProcessingState().getJdbcValuesSourceProcessingState().getProcessingOptions(); + return matchesOptionalInstance( data, processingOptions ) ? processingOptions.getEffectiveOptionalObject() : null; + } + else { + return null; + } + } + + // FIXME: I could change the scope of this method in ORM + private boolean isProxyInstance(Object proxy) { + return proxy != null + && ( proxy instanceof MapProxy || getEntityDescriptor().getJavaType().getJavaTypeClass().isInstance( proxy ) ); + } + + // FIXME: I could change the scope of this method in ORM + private Object resolveInstanceFromCache(ReactiveEntityInitializerData data) { + return CacheEntityLoaderHelper.INSTANCE.loadFromSecondLevelCache( + data.getRowProcessingState().getSession().asEventSource(), + null, + data.getLockMode(), + getEntityDescriptor(), + data.getEntityKey() + ); + } + + // FIXME: I could change the scope of this method in ORM + private boolean matchesOptionalInstance( + ReactiveEntityInitializerData data, + JdbcValuesSourceProcessingOptions processingOptions) { + final Object optionalEntityInstance = processingOptions.getEffectiveOptionalObject(); + final Object requestedEntityId = processingOptions.getEffectiveOptionalId(); + return requestedEntityId != null + && optionalEntityInstance != null + && requestedEntityId.equals( data.getEntityKey().getIdentifier() ); + } + + private boolean isExistingEntityInitialized(Object existingEntity) { + return Hibernate.isInitialized( existingEntity ); + } + + @Override + public CompletionStage reactiveResolveKey(EntityInitializerData data) { + return reactiveResolveKey( (ReactiveEntityInitializerData) data, false ); + } + + protected CompletionStage reactiveResolveKey(ReactiveEntityInitializerData data, boolean entityKeyOnly) { + // todo (6.0) : atm we do not handle sequential selects + // - see AbstractEntityPersister#hasSequentialSelect and + // AbstractEntityPersister#getSequentialSelect in 5.2 + if ( data.getState() != State.UNINITIALIZED ) { + return voidFuture(); + } + data.setState( State.KEY_RESOLVED ); + + // reset row state + data.setConcreteDescriptor( null ); + data.setEntityKey( null ); + data.setInstance( null ); + data.setEntityInstanceForNotify( null ); + data.setEntityHolder( null ); + + final Object[] id = new Object[1]; + return initializeId( data, id, entityKeyOnly ) + .thenCompose( initialized -> { + if ( initialized ) { + resolveEntityKey( data, id[0] ); + if ( !entityKeyOnly ) { + // Resolve the entity instance early as we have no key many-to-one + return reactiveResolveInstance( data ) + .thenCompose( v -> { + if ( !data.getShallowCached() ) { + if ( data.getState() == State.INITIALIZED ) { + if ( data.getEntityHolder().getEntityInitializer() == null ) { + // The entity is already part of the persistence context, + // so let's figure out the loaded state and only run sub-initializers if necessary + return reactiveResolveInstanceSubInitializers( data ); + } + // If the entity is initialized and getEntityInitializer() == this, + // we already processed a row for this entity before, + // but we still have to call resolveKeySubInitializers to activate sub-initializers, + // because a row might contain data that sub-initializers want to consume + else { + // todo: try to diff the eagerness of the sub-initializers to avoid further processing + return reactiveResolveKeySubInitializers( data ); + } + } + else { + return reactiveResolveKeySubInitializers( data ); + } + } + return voidFuture(); + } ); + } + } + return voidFuture(); + } ); + } + + + protected CompletionStage reactiveResolveInstanceSubInitializers(ReactiveEntityInitializerData data) { + final Initializer[] initializers = getSubInitializers()[data.getConcreteDescriptor().getSubclassId()]; + if ( initializers.length == 0 ) { + return voidFuture(); + } + final EntityEntry entityEntry = data.getEntityHolder().getEntityEntry(); + final RowProcessingState rowProcessingState = data.getRowProcessingState(); + assert entityEntry == rowProcessingState.getSession() + .getPersistenceContextInternal() + .getEntry( data.getEntityInstanceForNotify() ); + final Object[] loadedState = entityEntry.getLoadedState(); + final Object[] state; + if ( loadedState == null ) { + if ( entityEntry.getStatus() == Status.READ_ONLY ) { + state = data.getConcreteDescriptor().getValues( data.getEntityInstanceForNotify() ); + } + else { + // This branch is entered when a load happens while a cache entry is assembling. + // The EntityEntry has the LOADING state, but the loaded state is still empty. + assert entityEntry.getStatus() == Status.LOADING; + // Just skip any initialization in this case as the cache entry assembling will take care of it + return voidFuture(); + } + } + else { + state = loadedState; + } + return loop( 0, initializers.length, i -> { + final Initializer initializer = initializers[i]; + if ( initializer != null ) { + final Object subInstance = state[i]; + if ( subInstance == UNFETCHED_PROPERTY ) { + if ( initializer instanceof ReactiveInitializer ) { + return ( (ReactiveInitializer) initializer ) + .reactiveResolveKey( rowProcessingState ); + } + else { + // Go through the normal initializer process + initializer.resolveKey( rowProcessingState ); + } + } + else { + if ( initializer instanceof ReactiveInitializer ) { + return ( (ReactiveInitializer) initializer ) + .reactiveResolveInstance( subInstance, rowProcessingState ); + } + else { + initializer.resolveInstance( subInstance, rowProcessingState ); + } + } + } + return voidFuture(); + } ); + } + + protected CompletionStage reactiveResolveKeySubInitializers(ReactiveEntityInitializerData data) { + final RowProcessingState rowProcessingState = data.getRowProcessingState(); + return loop( + getSubInitializers()[data.getConcreteDescriptor().getSubclassId()], + initializer -> { + if ( initializer != null ) { + if ( initializer instanceof ReactiveInitializer ) { + return ( (ReactiveInitializer) initializer ).reactiveResolveKey( rowProcessingState ); + } + initializer.resolveKey( rowProcessingState ); + } + return voidFuture(); + } + ); + } + + /** + * Return {@code true} if the identifier has been initialized + */ + private CompletionStage initializeId(ReactiveEntityInitializerData data, Object[] id, boolean entityKeyOnly) { + final RowProcessingState rowProcessingState = data.getRowProcessingState(); + if ( getIdentifierAssembler() == null ) { + id[0] = rowProcessingState.getEntityId(); + assert id[0] != null : "Initializer requires a not null id for loading"; + return trueFuture(); + } + else { + //noinspection unchecked + final Initializer initializer = (Initializer) getIdentifierAssembler().getInitializer(); + if ( initializer != null ) { + final InitializerData subData = initializer.getData( rowProcessingState ); + return ( (ReactiveInitializer) initializer ) + .reactiveResolveKey( subData ) + .thenCompose( v -> { + if ( subData.getState() == State.MISSING ) { + setMissing( data ); + return falseFuture(); + } + else { + data.setConcreteDescriptor( determineConcreteEntityDescriptor( + rowProcessingState, + getDiscriminatorAssembler(), + getEntityDescriptor() + ) ); + assert data.getConcreteDescriptor() != null; + if ( isKeyManyToOne() ) { + if ( !data.getShallowCached() && !entityKeyOnly ) { + resolveKeySubInitializers( data ); + } + return falseFuture(); + } + } + id[0] = getIdentifierAssembler().assemble( rowProcessingState ); + if ( id[0] == null ) { + setMissing( data ); + return falseFuture(); + } + return trueFuture(); + } ); + } + id[0] = getIdentifierAssembler().assemble( rowProcessingState ); + if ( id[0] == null ) { + setMissing( data ); + return falseFuture(); + } + return trueFuture(); + } + } + + @Override + protected EntityInitializerData createInitializerData(RowProcessingState rowProcessingState) { + return new ReactiveEntityInitializerData( this, rowProcessingState ); + } + + @Override + public CompletionStage forEachReactiveSubInitializer( + BiFunction, RowProcessingState, CompletionStage> consumer, + InitializerData data) { + final RowProcessingState rowProcessingState = data.getRowProcessingState(); + return voidFuture() + .thenCompose( v -> { + if ( getKeyAssembler() != null ) { + final Initializer initializer = getKeyAssembler().getInitializer(); + if ( initializer != null ) { + return consumer.apply( (ReactiveInitializer) initializer, rowProcessingState ); + } + } + return voidFuture(); + } ) + .thenCompose( v -> { + if ( getIdentifierAssembler() != null ) { + final Initializer initializer = getIdentifierAssembler().getInitializer(); + if ( initializer != null ) { + consumer.apply( (ReactiveInitializer) initializer, rowProcessingState ); + } + } + return voidFuture(); + } ) + .thenCompose( v -> { + final ReactiveEntityInitializerDataAdaptor entityInitializerData = new ReactiveEntityInitializerDataAdaptor( + (EntityInitializerData) data ); + if ( entityInitializerData.getConcreteDescriptor() == null ) { + return loop( getSubInitializers(), initializers -> + loop( initializers, initializer -> { + if ( initializer != null ) { + return consumer.apply( (ReactiveInitializer) initializer, rowProcessingState ); + } + return voidFuture(); + } ) + ); + } + else { + Initializer[] subInitializers = getSubInitializers()[entityInitializerData.getConcreteDescriptor() + .getSubclassId()]; + return loop( subInitializers, initializer -> consumer + .apply( (ReactiveInitializer) initializer, rowProcessingState ) + ); + } + } ); + } + + private static class ReactiveEntityInitializerDataAdaptor extends EntityInitializerData { + + public ReactiveEntityInitializerDataAdaptor(EntityInitializerData delegate) { + super( delegate ); + } + + public EntityPersister getConcreteDescriptor() { + return concreteDescriptor; + } + } + + @Override + public Object getResolvedInstance(EntityInitializerData data) { + return super.getResolvedInstance( data ); + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityJoinedFetchInitializer.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityJoinedFetchInitializer.java deleted file mode 100644 index 4cb7d5b00..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityJoinedFetchInitializer.java +++ /dev/null @@ -1,133 +0,0 @@ -/* Hibernate, Relational Persistence for Idiomatic Java - * - * SPDX-License-Identifier: Apache-2.0 - * Copyright: Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.reactive.sql.results.graph.entity.internal; - -import org.hibernate.FetchNotFoundException; -import org.hibernate.Hibernate; -import org.hibernate.LockMode; -import org.hibernate.annotations.NotFoundAction; -import org.hibernate.internal.log.LoggingHelper; -import org.hibernate.metamodel.mapping.AttributeMapping; -import org.hibernate.reactive.sql.results.graph.entity.ReactiveAbstractEntityInitializer; -import org.hibernate.spi.NavigablePath; -import org.hibernate.sql.results.graph.AssemblerCreationState; -import org.hibernate.sql.results.graph.DomainResult; -import org.hibernate.sql.results.graph.DomainResultAssembler; -import org.hibernate.sql.results.graph.Fetch; -import org.hibernate.sql.results.graph.FetchParentAccess; -import org.hibernate.sql.results.graph.entity.EntityLoadingLogging; -import org.hibernate.sql.results.graph.entity.EntityResultGraphNode; -import org.hibernate.sql.results.graph.entity.EntityValuedFetchable; -import org.hibernate.sql.results.jdbc.spi.RowProcessingState; - -/** - * Basically a copy of {@link org.hibernate.sql.results.graph.entity.internal.EntityJoinedFetchInitializer}. - * - * We could delegate but it would require override a huge amount of methods. - * - * @see org.hibernate.sql.results.graph.entity.internal.EntityJoinedFetchInitializer - */ -public class ReactiveEntityJoinedFetchInitializer extends ReactiveAbstractEntityInitializer { - - private static final String CONCRETE_NAME = ReactiveEntityJoinedFetchInitializer.class.getSimpleName(); - - private final DomainResultAssembler keyAssembler; - private final NotFoundAction notFoundAction; - - public ReactiveEntityJoinedFetchInitializer( - EntityResultGraphNode resultDescriptor, - EntityValuedFetchable referencedFetchable, - NavigablePath navigablePath, - LockMode lockMode, - NotFoundAction notFoundAction, - DomainResult keyResult, - DomainResult rowIdResult, - Fetch identifierFetch, - Fetch discriminatorFetch, - FetchParentAccess parentAccess, - AssemblerCreationState creationState) { - super( - resultDescriptor, - navigablePath, - lockMode, - identifierFetch, - discriminatorFetch, - rowIdResult, - parentAccess, - creationState - ); - assert getInitializedPart() == referencedFetchable; - this.notFoundAction = notFoundAction; - - this.keyAssembler = keyResult == null ? null : keyResult.createResultAssembler( this, creationState ); - } - - @Override - public void resolveKey(RowProcessingState rowProcessingState) { - if ( isParentShallowCached() ) { - state = State.MISSING; - } - else if ( state == State.UNINITIALIZED ) { - if ( shouldSkipInitializer( rowProcessingState ) ) { - state = State.MISSING; - return; - } - - super.resolveKey( rowProcessingState ); - - // super processes the foreign-key target column. here we - // need to also look at the foreign-key value column to check - // for a dangling foreign-key - - if ( keyAssembler != null ) { - final Object fkKeyValue = keyAssembler.assemble( rowProcessingState ); - if ( fkKeyValue != null ) { - if ( state == State.MISSING ) { - if ( notFoundAction != NotFoundAction.IGNORE ) { - throw new FetchNotFoundException( - getEntityDescriptor().getEntityName(), - fkKeyValue - ); - } - else { - EntityLoadingLogging.ENTITY_LOADING_LOGGER.debugf( - "Ignoring dangling foreign-key due to `@NotFound(IGNORE); association will be null - %s", - getNavigablePath() - ); - } - } - } - } - } - } - - @Override - public void initializeInstanceFromParent(Object parentInstance, RowProcessingState rowProcessingState) { - final AttributeMapping attributeMapping = getInitializedPart().asAttributeMapping(); - final Object instance = attributeMapping != null - ? attributeMapping.getValue( parentInstance ) - : parentInstance; - setEntityInstance( instance ); - setEntityInstanceForNotify( Hibernate.unproxy( instance ) ); - state = State.INITIALIZED; - initializeSubInstancesFromParent( rowProcessingState ); - } - - @Override - protected String getSimpleConcreteImplName() { - return CONCRETE_NAME; - } - - @Override - public boolean isResultInitializer() { - return false; - } - - @Override - public String toString() { - return "ReactiveEntityJoinedFetchInitializer(" + LoggingHelper.toLoggableString( getNavigablePath() ) + ")"; - } -} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityResultInitializer.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityResultInitializer.java deleted file mode 100644 index fde931db7..000000000 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntityResultInitializer.java +++ /dev/null @@ -1,64 +0,0 @@ -/* Hibernate, Relational Persistence for Idiomatic Java - * - * SPDX-License-Identifier: Apache-2.0 - * Copyright: Red Hat Inc. and Hibernate Authors - */ -package org.hibernate.reactive.sql.results.graph.entity.internal; - - -import org.hibernate.LockMode; -import org.hibernate.reactive.sql.results.graph.entity.ReactiveAbstractEntityInitializer; -import org.hibernate.spi.NavigablePath; -import org.hibernate.sql.results.graph.AssemblerCreationState; -import org.hibernate.sql.results.graph.DomainResult; -import org.hibernate.sql.results.graph.Fetch; -import org.hibernate.sql.results.graph.basic.BasicFetch; -import org.hibernate.sql.results.graph.entity.EntityResultGraphNode; - -/** - * @see org.hibernate.sql.results.graph.entity.internal.EntityResultInitializer - */ -public class ReactiveEntityResultInitializer extends ReactiveAbstractEntityInitializer { - private static final String CONCRETE_NAME = ReactiveEntityResultInitializer.class.getSimpleName(); - - public ReactiveEntityResultInitializer( - EntityResultGraphNode resultDescriptor, - NavigablePath navigablePath, - LockMode lockMode, - Fetch identifierFetch, - BasicFetch discriminatorFetch, - DomainResult rowIdResult, - AssemblerCreationState creationState) { - super( - resultDescriptor, - navigablePath, - lockMode, - identifierFetch, - discriminatorFetch, - rowIdResult, - null, - creationState - ); - } - - @Override - protected String getSimpleConcreteImplName() { - return CONCRETE_NAME; - } - - @Override - public String toString() { - return CONCRETE_NAME + "(" + getNavigablePath() + ")"; - } - - @Override - public boolean isPartOfKey() { - // The entity result itself can never be part of the key - return false; - } - - @Override - public boolean isResultInitializer() { - return true; - } -} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchByUniqueKeyInitializer.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchByUniqueKeyInitializer.java index 88300f7fb..61ad620db 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchByUniqueKeyInitializer.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchByUniqueKeyInitializer.java @@ -10,114 +10,82 @@ import org.hibernate.engine.spi.EntityUniqueKey; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.reactive.persister.entity.impl.ReactiveEntityPersister; -import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; -import org.hibernate.spi.EntityIdentifierNavigablePath; import org.hibernate.spi.NavigablePath; -import org.hibernate.sql.results.graph.DomainResultAssembler; -import org.hibernate.sql.results.graph.FetchParentAccess; +import org.hibernate.sql.results.graph.AssemblerCreationState; +import org.hibernate.sql.results.graph.DomainResult; +import org.hibernate.sql.results.graph.InitializerParent; +import org.hibernate.sql.results.graph.entity.internal.EntitySelectFetchInitializer; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; -import static org.hibernate.reactive.util.impl.CompletionStages.whileLoop; /** * @see org.hibernate.sql.results.graph.entity.internal.EntitySelectFetchByUniqueKeyInitializer */ -public class ReactiveEntitySelectFetchByUniqueKeyInitializer extends ReactiveEntitySelectFetchInitializer { +public class ReactiveEntitySelectFetchByUniqueKeyInitializer + extends ReactiveEntitySelectFetchInitializer { private final ToOneAttributeMapping fetchedAttribute; public ReactiveEntitySelectFetchByUniqueKeyInitializer( - FetchParentAccess parentAccess, + InitializerParent parent, ToOneAttributeMapping fetchedAttribute, NavigablePath fetchedNavigable, EntityPersister concreteDescriptor, - DomainResultAssembler keyAssembler) { - super( parentAccess, fetchedAttribute, fetchedNavigable, concreteDescriptor, keyAssembler ); + DomainResult keyResult, + boolean affectedByFilter, + AssemblerCreationState creationState) { + super( + parent, + fetchedAttribute, + fetchedNavigable, + concreteDescriptor, + keyResult, + affectedByFilter, + creationState + ); this.fetchedAttribute = fetchedAttribute; } @Override - public CompletionStage reactiveResolveInstance(ReactiveRowProcessingState rowProcessingState) { - if ( state != State.UNINITIALIZED ) { - return voidFuture(); - } - state = State.RESOLVED; - - // We can avoid processing further if the parent is already initialized or missing, - // as the value produced by this initializer will never be used anyway. - if ( parentShallowCached || shouldSkipInitializer( rowProcessingState ) ) { - state = State.INITIALIZED; - return voidFuture(); - } - - entityIdentifier = keyAssembler.assemble( rowProcessingState ); - if ( entityIdentifier == null ) { - state = State.INITIALIZED; - return voidFuture(); - } - - NavigablePath[] np = { getNavigablePath().getParent() }; - if ( np[0] == null ) { - return voidFuture(); - } - return whileLoop( () -> { - CompletionStage loop = voidFuture(); - // Defer the select by default to the initialize phase - // We only need to select in this phase if this is part of an identifier or foreign key - if ( np[0] instanceof EntityIdentifierNavigablePath - || ForeignKeyDescriptor.PART_NAME.equals( np[0].getLocalName() ) - || ForeignKeyDescriptor.TARGET_PART_NAME.equals( np[0].getLocalName() ) ) { - loop = reactiveInitializeInstance( rowProcessingState ); - } - return loop.thenApply( v -> { - np[0] = np[0].getParent(); - return np[0] != null; - } ); - } ); - } - - @Override - public CompletionStage reactiveInitializeInstance(ReactiveRowProcessingState rowProcessingState) { - if ( state != State.RESOLVED ) { - return voidFuture(); - } - state = State.INITIALIZED; - + public CompletionStage reactiveInitialize(EntitySelectFetchInitializerData actual) { + final ReactiveEntitySelectFetchInitializerData data = (ReactiveEntitySelectFetchInitializerData) actual; final String entityName = concreteDescriptor.getEntityName(); final String uniqueKeyPropertyName = fetchedAttribute.getReferencedPropertyName(); - final SharedSessionContractImplementor session = rowProcessingState.getSession(); + + final SharedSessionContractImplementor session = data.getRowProcessingState().getSession(); + final EntityUniqueKey euk = new EntityUniqueKey( entityName, uniqueKeyPropertyName, - entityIdentifier, + data.getEntityIdentifier(), concreteDescriptor.getPropertyType( uniqueKeyPropertyName ), session.getFactory() ); final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); - setEntityInstance( persistenceContext.getEntity( euk ) ); - if ( entityInstance == null ) { + data.setInstance( persistenceContext.getEntity( euk ) ); + if ( data.getInstance() == null ) { return ( (ReactiveEntityPersister) concreteDescriptor ) - .reactiveLoadByUniqueKey( uniqueKeyPropertyName, entityIdentifier, session ) - .thenAccept( this::setEntityInstance ) + .reactiveLoadByUniqueKey( uniqueKeyPropertyName, data.getEntityIdentifier(), session ) + .thenAccept( data::setInstance ) .thenAccept( v -> { // If the entity was not in the Persistence Context, but was found now, // add it to the Persistence Context - if ( entityInstance != null ) { - persistenceContext.addEntity( euk, entityInstance ); + if ( data.getInstance() != null ) { + persistenceContext.addEntity( euk, data.getInstance() ); } } ); } - if ( entityInstance != null ) { - setEntityInstance( persistenceContext.proxyFor( entityInstance ) ); + if ( data.getInstance() != null ) { + data.setInstance( persistenceContext.proxyFor( data.getInstance() ) ); } return voidFuture(); } - private void setEntityInstance(Object instance) { - entityInstance = instance; + @Override + public Object getResolvedInstance(EntitySelectFetchInitializerData data) { + return super.getResolvedInstance( data ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchInitializer.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchInitializer.java index 1a9d14f4c..b27c0cc93 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchInitializer.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchInitializer.java @@ -7,7 +7,9 @@ import java.lang.invoke.MethodHandles; import java.util.concurrent.CompletionStage; +import java.util.function.BiFunction; +import org.hibernate.EntityFilterException; import org.hibernate.FetchNotFoundException; import org.hibernate.Hibernate; import org.hibernate.annotations.NotFoundAction; @@ -15,268 +17,203 @@ import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.internal.util.StringHelper; -import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.LazyInitializer; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.logging.impl.LoggerFactory; -import org.hibernate.reactive.session.impl.ReactiveQueryExecutorLookup; -import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; +import org.hibernate.reactive.session.ReactiveSession; import org.hibernate.reactive.sql.results.graph.ReactiveInitializer; -import org.hibernate.spi.EntityIdentifierNavigablePath; import org.hibernate.spi.NavigablePath; -import org.hibernate.sql.results.graph.DomainResultAssembler; -import org.hibernate.sql.results.graph.FetchParentAccess; -import org.hibernate.sql.results.graph.entity.EntityLoadingLogging; +import org.hibernate.sql.results.graph.AssemblerCreationState; +import org.hibernate.sql.results.graph.DomainResult; +import org.hibernate.sql.results.graph.Initializer; +import org.hibernate.sql.results.graph.InitializerData; +import org.hibernate.sql.results.graph.InitializerParent; import org.hibernate.sql.results.graph.entity.internal.EntitySelectFetchInitializer; import org.hibernate.sql.results.jdbc.spi.RowProcessingState; -import static org.hibernate.internal.log.LoggingHelper.toLoggableString; -import static org.hibernate.reactive.util.impl.CompletionStages.failedFuture; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; -import static org.hibernate.reactive.util.impl.CompletionStages.whileLoop; -import static org.hibernate.sql.results.graph.entity.EntityLoadingLogging.ENTITY_LOADING_LOGGER; /** * @see org.hibernate.sql.results.graph.entity.internal.EntitySelectFetchInitializer */ -public class ReactiveEntitySelectFetchInitializer extends EntitySelectFetchInitializer implements ReactiveInitializer { +public class ReactiveEntitySelectFetchInitializer + extends EntitySelectFetchInitializer implements ReactiveInitializer { - private static final String CONCRETE_NAME = ReactiveEntitySelectFetchInitializer.class.getSimpleName(); + public static class ReactiveEntitySelectFetchInitializerData + extends EntitySelectFetchInitializer.EntitySelectFetchInitializerData { + + public ReactiveEntitySelectFetchInitializerData(EntitySelectFetchInitializer initializer, RowProcessingState rowProcessingState) { + super( initializer, rowProcessingState ); + } + + public Object getEntityIdentifier() { + return entityIdentifier; + } + + public void setEntityIdentifier(Object entityIdentifier) { + super.entityIdentifier = entityIdentifier; + } + } private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); private final boolean isEnhancedForLazyLoading; public ReactiveEntitySelectFetchInitializer( - FetchParentAccess parentAccess, - ToOneAttributeMapping toOneMapping, + InitializerParent parent, + ToOneAttributeMapping fetchedAttribute, NavigablePath fetchedNavigable, EntityPersister concreteDescriptor, - DomainResultAssembler keyAssembler) { - super( parentAccess, toOneMapping, fetchedNavigable, concreteDescriptor, keyAssembler ); + DomainResult keyResult, + boolean affectedByFilter, + AssemblerCreationState creationState) { + super( + parent, + fetchedAttribute, + fetchedNavigable, + concreteDescriptor, + keyResult, + affectedByFilter, + creationState + ); this.isEnhancedForLazyLoading = concreteDescriptor.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading(); } + @Override + protected InitializerData createInitializerData(RowProcessingState rowProcessingState) { + return new ReactiveEntitySelectFetchInitializerData( this, rowProcessingState ); + } + @Override public void resolveInstance(RowProcessingState rowProcessingState) { super.resolveInstance( rowProcessingState ); } @Override - public void initializeInstance(RowProcessingState rowProcessingState) { + public void initializeInstance(EntitySelectFetchInitializerData data) { throw LOG.nonReactiveMethodCall( "reactiveInitializeInstance" ); } @Override - public CompletionStage reactiveResolveInstance(ReactiveRowProcessingState rowProcessingState) { - if ( state != State.UNINITIALIZED ) { - return voidFuture(); - } - state = State.RESOLVED; - - // We can avoid processing further if the parent is already initialized or missing, - // as the value produced by this initializer will never be used anyway. - if ( parentShallowCached || shouldSkipInitializer( rowProcessingState ) ) { - state = State.INITIALIZED; - return voidFuture(); - } - - entityIdentifier = keyAssembler.assemble( rowProcessingState ); - if ( entityIdentifier == null ) { - state = State.INITIALIZED; - return voidFuture(); - } - - if ( EntityLoadingLogging.ENTITY_LOADING_LOGGER.isTraceEnabled() ) { - EntityLoadingLogging.ENTITY_LOADING_LOGGER.tracef( - "(%s) Beginning Initializer#resolveInstance process for entity (%s) : %s", - StringHelper.collapse( this.getClass().getName() ), - getNavigablePath(), - entityIdentifier - ); - } - final SharedSessionContractImplementor session = rowProcessingState.getSession(); - final EntityKey entityKey = new EntityKey( entityIdentifier, concreteDescriptor ); - - final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); - final EntityHolder holder = persistenceContext.getEntityHolder( entityKey ); - if ( holder != null ) { - if ( EntityLoadingLogging.ENTITY_LOADING_LOGGER.isDebugEnabled() ) { - EntityLoadingLogging.ENTITY_LOADING_LOGGER.debugf( - "(%s) Found existing loading entry [%s] - using loading instance", - CONCRETE_NAME, - toLoggableString( - getNavigablePath(), - entityIdentifier - ) - ); - } - entityInstance = holder.getEntity(); - if ( holder.getEntityInitializer() == null ) { - if ( entityInstance != null && Hibernate.isInitialized( entityInstance ) ) { - state = State.INITIALIZED; - return voidFuture(); - } - } - else if ( holder.getEntityInitializer() != this ) { - // the entity is already being loaded elsewhere - if ( EntityLoadingLogging.ENTITY_LOADING_LOGGER.isDebugEnabled() ) { - EntityLoadingLogging.ENTITY_LOADING_LOGGER.debugf( - "(%s) Entity [%s] being loaded by another initializer [%s] - skipping processing", - CONCRETE_NAME, - toLoggableString( getNavigablePath(), entityIdentifier ), - holder.getEntityInitializer() - ); - } - state = State.INITIALIZED; - return voidFuture(); - } - else if ( entityInstance == null ) { - state = State.INITIALIZED; - return voidFuture(); - } - } - - NavigablePath[] np = { getNavigablePath().getParent() }; - if ( np[0] == null ) { - return voidFuture(); - } - return whileLoop( () -> { - CompletionStage loop = voidFuture(); - // Defer the select by default to the initialize phase - // We only need to select in this phase if this is part of an identifier or foreign key - if ( np[0] instanceof EntityIdentifierNavigablePath - || ForeignKeyDescriptor.PART_NAME.equals( np[0].getLocalName() ) - || ForeignKeyDescriptor.TARGET_PART_NAME.equals( np[0].getLocalName() ) ) { - loop = reactiveInitializeInstance( rowProcessingState ); - } - return loop.thenApply( v -> { - np[0] = np[0].getParent(); - return np[0] != null; - } ); - } ); + protected void initialize(EntitySelectFetchInitializerData data) { + throw LOG.nonReactiveMethodCall( "reactiveInitialize" ); } @Override - public CompletionStage reactiveInitializeInstance(ReactiveRowProcessingState rowProcessingState) { - if ( state != State.RESOLVED ) { - return voidFuture(); - } - state = State.INITIALIZED; - - // We can avoid processing further if the parent is already initialized or missing, - // as the value produced by this initializer will never be used anyway. - if ( parentShallowCached || shouldSkipInitializer( rowProcessingState ) ) { - initializeState(); - return voidFuture(); - } - - entityIdentifier = keyAssembler.assemble( rowProcessingState ); - if ( entityIdentifier == null ) { - initializeState(); - return voidFuture(); - } - - if ( ENTITY_LOADING_LOGGER.isTraceEnabled() ) { - ENTITY_LOADING_LOGGER.tracef( - "(%s) Beginning Initializer#resolveInstance process for entity (%s) : %s", - StringHelper.collapse( this.getClass().getName() ), - getNavigablePath(), - entityIdentifier - ); + public CompletionStage forEachReactiveSubInitializer( + BiFunction, RowProcessingState, CompletionStage> consumer, + InitializerData data) { + Initializer initializer = getKeyAssembler().getInitializer(); + if ( initializer != null ) { + return consumer.apply( (ReactiveInitializer) initializer, data.getRowProcessingState() ); } + return voidFuture(); + } + protected CompletionStage reactiveInitialize(EntitySelectFetchInitializerData ormData) { + ReactiveEntitySelectFetchInitializerData data = (ReactiveEntitySelectFetchInitializerData) ormData; + final RowProcessingState rowProcessingState = data.getRowProcessingState(); final SharedSessionContractImplementor session = rowProcessingState.getSession(); - final String entityName = concreteDescriptor.getEntityName(); - final EntityKey entityKey = new EntityKey( entityIdentifier, concreteDescriptor ); + final EntityKey entityKey = new EntityKey( data.getEntityIdentifier(), concreteDescriptor ); final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); final EntityHolder holder = persistenceContext.getEntityHolder( entityKey ); if ( holder != null ) { - if ( EntityLoadingLogging.ENTITY_LOADING_LOGGER.isDebugEnabled() ) { - EntityLoadingLogging.ENTITY_LOADING_LOGGER.debugf( - "(%s) Found existing loading entry [%s] - using loading instance", - CONCRETE_NAME, - toLoggableString( - getNavigablePath(), - entityIdentifier - ) - ); - } - entityInstance = holder.getEntity(); + data.setInstance( persistenceContext.proxyFor( holder, concreteDescriptor ) ); if ( holder.getEntityInitializer() == null ) { - if ( entityInstance != null && Hibernate.isInitialized( entityInstance ) ) { - initializeState(); + if ( data.getInstance() != null && Hibernate.isInitialized( data.getInstance() ) ) { + data.setState( State.INITIALIZED ); return voidFuture(); } } else if ( holder.getEntityInitializer() != this ) { // the entity is already being loaded elsewhere - if ( EntityLoadingLogging.ENTITY_LOADING_LOGGER.isDebugEnabled() ) { - EntityLoadingLogging.ENTITY_LOADING_LOGGER.debugf( - "(%s) Entity [%s] being loaded by another initializer [%s] - skipping processing", - CONCRETE_NAME, - toLoggableString( getNavigablePath(), entityIdentifier ), - holder.getEntityInitializer() - ); - } - initializeState(); + data.setState( State.INITIALIZED ); return voidFuture(); } - else if ( entityInstance == null ) { - initializeState(); + else if ( data.getInstance() == null ) { + // todo: maybe mark this as resolved instead? + assert holder.getProxy() == null : "How to handle this case?"; + data.setState( State.INITIALIZED ); return voidFuture(); } } + data.setState( State.INITIALIZED ); + final String entityName = concreteDescriptor.getEntityName(); - if ( ENTITY_LOADING_LOGGER.isDebugEnabled() ) { - ENTITY_LOADING_LOGGER.debugf( - "(%s) Invoking session#internalLoad for entity (%s) : %s", - CONCRETE_NAME, - toLoggableString( getNavigablePath(), entityIdentifier ), - entityIdentifier - ); - } - - return ReactiveQueryExecutorLookup.extract( session ) - .reactiveInternalLoad( entityName, entityIdentifier, true, toOneMapping().isInternalLoadNullable() ) - .thenCompose( instance -> { - entityInstance = instance; - - if ( entityInstance == null ) { - if ( toOneMapping().getNotFoundAction() == NotFoundAction.EXCEPTION ) { - return failedFuture( new FetchNotFoundException( entityName, entityIdentifier ) ); + return ( (ReactiveSession) session ).reactiveInternalLoad( + entityName, + data.getEntityIdentifier(), + true, + toOneMapping.isInternalLoadNullable() + ) + .thenAccept( instance -> { + data.setInstance( instance ); + + if ( instance == null ) { + if ( toOneMapping.getNotFoundAction() != NotFoundAction.IGNORE ) { + if ( affectedByFilter ) { + throw new EntityFilterException( + entityName, + data.getEntityIdentifier(), + toOneMapping.getNavigableRole().getFullPath() + ); + } + if ( toOneMapping.getNotFoundAction() == NotFoundAction.EXCEPTION ) { + throw new FetchNotFoundException( entityName, data.getEntityIdentifier() ); + } } - } - - if ( ENTITY_LOADING_LOGGER.isDebugEnabled() ) { - ENTITY_LOADING_LOGGER.debugf( - "(%s) Entity [%s] : %s has being loaded by session.internalLoad.", - CONCRETE_NAME, - toLoggableString( getNavigablePath(), entityIdentifier ), - entityIdentifier + rowProcessingState.getSession().getPersistenceContextInternal().claimEntityHolderIfPossible( + new EntityKey( data.getEntityIdentifier(), concreteDescriptor ), + null, + rowProcessingState.getJdbcValuesSourceProcessingState(), + this ); } - final boolean unwrapProxy = toOneMapping().isUnwrapProxy() && isEnhancedForLazyLoading; - final LazyInitializer lazyInitializer = HibernateProxy.extractLazyInitializer( entityInstance ); + final boolean unwrapProxy = toOneMapping.isUnwrapProxy() && isEnhancedForLazyLoading; + final LazyInitializer lazyInitializer = HibernateProxy.extractLazyInitializer( data.getInstance() ); if ( lazyInitializer != null ) { lazyInitializer.setUnwrap( unwrapProxy ); } - initializeState(); - return voidFuture(); } ); } - protected void initializeState() { - state = State.INITIALIZED; + @Override + public CompletionStage reactiveResolveInstance(Data original) { + if ( original.getState() != State.KEY_RESOLVED ) { + return voidFuture(); + } + + ReactiveEntitySelectFetchInitializerData data = (ReactiveEntitySelectFetchInitializerData) original; + final RowProcessingState rowProcessingState = data.getRowProcessingState(); + data.setEntityIdentifier( keyAssembler.assemble( rowProcessingState ) ); + + if ( data.getEntityIdentifier() == null ) { + data.setState( State.MISSING ); + data.setInstance( null ); + return voidFuture(); + } + + data.setState( State.INITIALIZED ); + return reactiveInitialize( data ); } - protected ToOneAttributeMapping toOneMapping() { - return (ToOneAttributeMapping) getInitializedPart(); + @Override + public CompletionStage reactiveInitializeInstance(Data data) { + if ( data.getState() != State.RESOLVED ) { + return voidFuture(); + } + data.setState( State.INITIALIZED ); + Hibernate.initialize( data.getInstance() ); + return voidFuture(); + } + + @Override + public Object getResolvedInstance(Data data) { + return super.getResolvedInstance( data ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchInitializerBuilder.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchInitializerBuilder.java index 34d25c6bc..c4cb87838 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchInitializerBuilder.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/graph/entity/internal/ReactiveEntitySelectFetchInitializerBuilder.java @@ -11,11 +11,13 @@ import org.hibernate.metamodel.mapping.EntityValuedModelPart; import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.reactive.sql.results.graph.embeddable.internal.ReactiveEmbeddableForeignKeyResultImpl; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.results.graph.AssemblerCreationState; import org.hibernate.sql.results.graph.DomainResult; -import org.hibernate.sql.results.graph.FetchParentAccess; +import org.hibernate.sql.results.graph.InitializerParent; import org.hibernate.sql.results.graph.embeddable.EmbeddableInitializer; +import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableForeignKeyResultImpl; import org.hibernate.sql.results.graph.entity.EntityInitializer; import org.hibernate.sql.results.graph.entity.internal.BatchEntityInsideEmbeddableSelectFetchInitializer; import org.hibernate.sql.results.graph.entity.internal.BatchEntitySelectFetchInitializer; @@ -26,74 +28,94 @@ */ public class ReactiveEntitySelectFetchInitializerBuilder { - public static EntityInitializer createInitializer( - FetchParentAccess parentAccess, + public static EntityInitializer createInitializer( + InitializerParent parent, ToOneAttributeMapping fetchedAttribute, EntityPersister entityPersister, - DomainResult keyResult, + DomainResult originalKeyResult, NavigablePath navigablePath, boolean selectByUniqueKey, + boolean affectedByFilter, AssemblerCreationState creationState) { + final DomainResult keyResult = originalKeyResult instanceof EmbeddableForeignKeyResultImpl + ? new ReactiveEmbeddableForeignKeyResultImpl<>( (EmbeddableForeignKeyResultImpl) originalKeyResult ) + : originalKeyResult; if ( selectByUniqueKey ) { return new ReactiveEntitySelectFetchByUniqueKeyInitializer( - parentAccess, + parent, fetchedAttribute, navigablePath, entityPersister, - keyResult.createResultAssembler( parentAccess, creationState ) + keyResult, + affectedByFilter, + creationState ); } - final BatchMode batchMode = determineBatchMode( entityPersister, parentAccess, creationState ); + final BatchMode batchMode = determineBatchMode( entityPersister, parent, creationState ); switch ( batchMode ) { case NONE: - return new ReactiveEntitySelectFetchInitializer( - parentAccess, + return new ReactiveEntitySelectFetchInitializer<>( + parent, fetchedAttribute, navigablePath, entityPersister, - keyResult.createResultAssembler( parentAccess, creationState ) + keyResult, + affectedByFilter, + creationState ); case BATCH_LOAD: - if ( parentAccess.isEmbeddableInitializer() ) { + if ( parent.isEmbeddableInitializer() ) { return new BatchEntityInsideEmbeddableSelectFetchInitializer( - parentAccess, + parent, fetchedAttribute, navigablePath, entityPersister, - keyResult.createResultAssembler( parentAccess, creationState ) + keyResult, + affectedByFilter, + creationState ); } else { return new BatchEntitySelectFetchInitializer( - parentAccess, + parent, fetchedAttribute, navigablePath, entityPersister, - keyResult.createResultAssembler( parentAccess, creationState ) + keyResult, + affectedByFilter, + creationState ); } case BATCH_INITIALIZE: return new BatchInitializeEntitySelectFetchInitializer( - parentAccess, + parent, fetchedAttribute, navigablePath, entityPersister, - keyResult.createResultAssembler( parentAccess, creationState ) + keyResult, + affectedByFilter, + creationState ); } throw new IllegalStateException( "Should be unreachable" ); } // FIXME: Use the one in ORM - private static BatchMode determineBatchMode( + public static BatchMode determineBatchMode( EntityPersister entityPersister, - FetchParentAccess parentAccess, + InitializerParent parent, AssemblerCreationState creationState) { - if ( !entityPersister.isBatchLoadable() || creationState.isScrollResult() ) { + if ( !entityPersister.isBatchLoadable() ) { return BatchMode.NONE; } - while ( parentAccess.isEmbeddableInitializer() ) { - final EmbeddableInitializer embeddableInitializer = parentAccess.asEmbeddableInitializer(); + if ( creationState.isDynamicInstantiation() ) { + if ( canBatchInitializeBeUsed( entityPersister ) ) { + return BatchMode.BATCH_INITIALIZE; + } + return BatchMode.NONE; + } + while ( parent.isEmbeddableInitializer() ) { + final EmbeddableInitializer embeddableInitializer = parent.asEmbeddableInitializer(); final EmbeddableValuedModelPart initializedPart = embeddableInitializer.getInitializedPart(); // For entity identifier mappings we can't batch load, // because the entity identifier needs the instance in the resolveKey phase, @@ -101,29 +123,40 @@ private static BatchMode determineBatchMode( if ( initializedPart.isEntityIdentifierMapping() // todo: check if the virtual check is necessary || initializedPart.isVirtual() - // If the parent embeddable has a custom instantiator, we can't inject entities later through setValues() - || !( initializedPart.getMappedType().getRepresentationStrategy().getInstantiator() instanceof StandardEmbeddableInstantiator ) ) { + || initializedPart.getMappedType().isPolymorphic() + // If the parent embeddable has a custom instantiator, + // we can't inject entities later through setValues() + || !( initializedPart.getMappedType().getRepresentationStrategy().getInstantiator() + instanceof StandardEmbeddableInstantiator ) ) { return entityPersister.hasSubclasses() ? BatchMode.NONE : BatchMode.BATCH_INITIALIZE; } - parentAccess = parentAccess.getFetchParentAccess(); - if ( parentAccess == null ) { + parent = parent.getParent(); + if ( parent == null ) { break; } } - if ( parentAccess != null ) { - assert parentAccess.getInitializedPart() instanceof EntityValuedModelPart; - final EntityPersister parentPersister = parentAccess.asEntityInitializer().getEntityDescriptor(); + if ( parent != null ) { + assert parent.getInitializedPart() instanceof EntityValuedModelPart; + final EntityPersister parentPersister = parent.asEntityInitializer().getEntityDescriptor(); final EntityDataAccess cacheAccess = parentPersister.getCacheAccessStrategy(); if ( cacheAccess != null ) { // Do batch initialization instead of batch loading if the parent entity is cacheable // to avoid putting entity state into the cache at a point when the association is not yet set - return BatchMode.BATCH_INITIALIZE; + if ( canBatchInitializeBeUsed( entityPersister ) ) { + return BatchMode.BATCH_INITIALIZE; + } + return BatchMode.NONE; } } return BatchMode.BATCH_LOAD; } - enum BatchMode { + private static boolean canBatchInitializeBeUsed(EntityPersister entityPersister) { + // we need to create a Proxy in order to use batch initialize + return entityPersister.getRepresentationStrategy().getProxyFactory() != null; + } + + private enum BatchMode { NONE, BATCH_LOAD, BATCH_INITIALIZE diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDeferredResultSetAccess.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDeferredResultSetAccess.java index 09a2afaa2..da94cc329 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDeferredResultSetAccess.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDeferredResultSetAccess.java @@ -6,13 +6,11 @@ package org.hibernate.reactive.sql.results.internal; import java.lang.invoke.MethodHandles; -import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionStage; -import java.util.function.Function; import org.hibernate.HibernateException; import org.hibernate.dialect.Dialect; @@ -32,6 +30,7 @@ import org.hibernate.sql.exec.spi.ExecutionContext; import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect; import org.hibernate.sql.exec.spi.JdbcParameterBindings; +import org.hibernate.sql.exec.spi.JdbcSelectExecutor; import org.hibernate.sql.results.jdbc.internal.DeferredResultSetAccess; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; import org.hibernate.type.BasicType; @@ -50,7 +49,6 @@ public class ReactiveDeferredResultSetAccess extends DeferredResultSetAccess imp private CompletionStage resultSetStage; - private Integer columnCount; private ResultSet resultSet; @@ -58,8 +56,9 @@ public ReactiveDeferredResultSetAccess( JdbcOperationQuerySelect jdbcSelect, JdbcParameterBindings jdbcParameterBindings, ExecutionContext executionContext, - Function statementCreator) { - super( jdbcSelect, jdbcParameterBindings, executionContext, statementCreator ); + JdbcSelectExecutor.StatementCreator statementCreator, + int resultCountEstimate) { + super( jdbcSelect, jdbcParameterBindings, executionContext, statementCreator, resultCountEstimate ); this.executionContext = executionContext; this.sqlStatementLogger = executionContext.getSession().getJdbcServices().getSqlStatementLogger(); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDirectResultSetAccess.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDirectResultSetAccess.java index 094fb28ad..2412f3941 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDirectResultSetAccess.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveDirectResultSetAccess.java @@ -53,6 +53,11 @@ public void release() { .release( resultSet, resultSetSource ); } + @Override + public int getResultCountEstimate() { + return super.getResultCountEstimate(); + } + @Override public BasicType resolveType(int position, JavaType explicitJavaType, SessionFactoryImplementor sessionFactory) { return super.resolveType( position, explicitJavaType, sessionFactory ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveEntityDelayedFetchImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveEntityDelayedFetchImpl.java index 5fc1a50b7..7adeab775 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveEntityDelayedFetchImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveEntityDelayedFetchImpl.java @@ -6,13 +6,16 @@ package org.hibernate.reactive.sql.results.internal; import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; +import org.hibernate.reactive.sql.results.graph.entity.internal.ReactiveEntityAssembler; import org.hibernate.reactive.sql.results.graph.entity.internal.ReactiveEntityDelayedFetchInitializer; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.results.graph.AssemblerCreationState; import org.hibernate.sql.results.graph.DomainResult; +import org.hibernate.sql.results.graph.DomainResultCreationState; import org.hibernate.sql.results.graph.FetchParent; -import org.hibernate.sql.results.graph.FetchParentAccess; +import org.hibernate.sql.results.graph.InitializerParent; import org.hibernate.sql.results.graph.entity.EntityInitializer; +import org.hibernate.sql.results.graph.entity.internal.EntityAssembler; import org.hibernate.sql.results.graph.entity.internal.EntityDelayedFetchImpl; public class ReactiveEntityDelayedFetchImpl extends EntityDelayedFetchImpl { @@ -21,20 +24,26 @@ public ReactiveEntityDelayedFetchImpl( ToOneAttributeMapping fetchedAttribute, NavigablePath navigablePath, DomainResult keyResult, - boolean selectByUniqueKey) { - super( fetchParent, fetchedAttribute, navigablePath, keyResult, selectByUniqueKey ); + boolean selectByUniqueKey, + DomainResultCreationState creationState) { + super( fetchParent, fetchedAttribute, navigablePath, keyResult, selectByUniqueKey, creationState ); } @Override - public EntityInitializer createInitializer(FetchParentAccess parentAccess, AssemblerCreationState creationState) { - return new ReactiveEntityDelayedFetchInitializer( parentAccess, - getNavigablePath(), - getEntityValuedModelPart(), - isSelectByUniqueKey(), - getKeyResult().createResultAssembler( - parentAccess, - creationState - ) + public EntityInitializer createInitializer(InitializerParent parent, AssemblerCreationState creationState) { + return new ReactiveEntityDelayedFetchInitializer( + parent, + getNavigablePath(), + getEntityValuedModelPart(), + isSelectByUniqueKey(), + getKeyResult(), + getDiscriminatorFetch(), + creationState ); } + + @Override + protected EntityAssembler buildEntityAssembler(EntityInitializer entityInitializer) { + return new ReactiveEntityAssembler( getFetchedMapping().getJavaType(), entityInitializer ); + } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveEntityResultImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveEntityResultImpl.java index 1d2adc4ee..819ba05c6 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveEntityResultImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveEntityResultImpl.java @@ -5,18 +5,31 @@ */ package org.hibernate.reactive.sql.results.internal; +import org.hibernate.annotations.NotFoundAction; +import org.hibernate.engine.FetchTiming; import org.hibernate.metamodel.mapping.EntityValuedModelPart; +import org.hibernate.reactive.logging.impl.Log; +import org.hibernate.reactive.sql.results.graph.embeddable.internal.ReactiveNonAggregatedIdentifierMappingFetch; import org.hibernate.reactive.sql.results.graph.entity.internal.ReactiveEntityAssembler; -import org.hibernate.reactive.sql.results.graph.entity.internal.ReactiveEntityResultInitializer; +import org.hibernate.reactive.sql.results.graph.entity.internal.ReactiveEntityInitializerImpl; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.results.graph.AssemblerCreationState; import org.hibernate.sql.results.graph.DomainResultAssembler; -import org.hibernate.sql.results.graph.FetchParentAccess; +import org.hibernate.sql.results.graph.DomainResultCreationState; +import org.hibernate.sql.results.graph.Fetch; +import org.hibernate.sql.results.graph.Fetchable; import org.hibernate.sql.results.graph.Initializer; +import org.hibernate.sql.results.graph.InitializerParent; +import org.hibernate.sql.results.graph.embeddable.internal.NonAggregatedIdentifierMappingFetch; import org.hibernate.sql.results.graph.entity.internal.EntityResultImpl; +import static java.lang.invoke.MethodHandles.lookup; +import static org.hibernate.reactive.logging.impl.LoggerFactory.make; + public class ReactiveEntityResultImpl extends EntityResultImpl { + private static final Log LOG = make( Log.class, lookup() ); + public ReactiveEntityResultImpl( NavigablePath navigablePath, EntityValuedModelPart entityValuedModelPart, @@ -26,23 +39,51 @@ public ReactiveEntityResultImpl( } @Override - public DomainResultAssembler createResultAssembler( - FetchParentAccess parentAccess, + public DomainResultAssembler createResultAssembler( + InitializerParent parent, AssemblerCreationState creationState) { - final Initializer initializer = creationState.resolveInitializer( - getNavigablePath(), - getReferencedModePart(), - () -> new ReactiveEntityResultInitializer( - this, - getNavigablePath(), - getLockMode( creationState ), - getIdentifierFetch(), - getDiscriminatorFetch(), - getRowIdResult(), - creationState - ) + return new ReactiveEntityAssembler( + this.getResultJavaType(), + creationState.resolveInitializer( this, parent, this ).asEntityInitializer() ); + } - return new ReactiveEntityAssembler( this.getResultJavaType(), initializer.asEntityInitializer() ); + @Override + public Initializer createInitializer(InitializerParent parent, AssemblerCreationState creationState) { + return new ReactiveEntityInitializerImpl( + this, + getSourceAlias(), + getIdentifierFetch(), + getDiscriminatorFetch(), + null, + getRowIdResult(), + NotFoundAction.EXCEPTION, + false, + null, + true, + creationState + ); + } + + @Override + public Fetch generateFetchableFetch( + Fetchable fetchable, + NavigablePath fetchablePath, + FetchTiming fetchTiming, + boolean selected, + String resultVariable, + DomainResultCreationState creationState) { + Fetch fetch = super.generateFetchableFetch( + fetchable, + fetchablePath, + fetchTiming, + selected, + resultVariable, + creationState + ); + if ( fetch instanceof NonAggregatedIdentifierMappingFetch ) { + return new ReactiveNonAggregatedIdentifierMappingFetch( (NonAggregatedIdentifierMappingFetch) fetch ); + } + return fetch; } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveInitializersList.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveInitializersList.java index e752a49ca..312f510f5 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveInitializersList.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveInitializersList.java @@ -8,19 +8,11 @@ import java.util.ArrayList; import java.util.Map; -import java.util.concurrent.CompletionStage; -import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; -import org.hibernate.reactive.sql.results.graph.ReactiveInitializer; import org.hibernate.spi.NavigablePath; -import org.hibernate.sql.exec.spi.ExecutionContext; import org.hibernate.sql.results.graph.Initializer; import org.hibernate.sql.results.graph.entity.internal.EntityDelayedFetchInitializer; import org.hibernate.sql.results.graph.entity.internal.EntitySelectFetchInitializer; -import org.hibernate.sql.results.jdbc.spi.RowProcessingState; - -import static org.hibernate.reactive.util.impl.CompletionStages.loop; -import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; /** * @see org.hibernate.sql.results.internal.InitializersList @@ -28,74 +20,16 @@ public final class ReactiveInitializersList { private final Initializer[] initializers; - private final Initializer[] sortedNonCollectionsFirst; private final Initializer[] sortedForResolveInstance; private final boolean hasCollectionInitializers; - private final Map initializerMap; private ReactiveInitializersList( Initializer[] initializers, - Initializer[] sortedNonCollectionsFirst, Initializer[] sortedForResolveInstance, - boolean hasCollectionInitializers, - Map initializerMap) { + boolean hasCollectionInitializers) { this.initializers = initializers; - this.sortedNonCollectionsFirst = sortedNonCollectionsFirst; this.sortedForResolveInstance = sortedForResolveInstance; this.hasCollectionInitializers = hasCollectionInitializers; - this.initializerMap = initializerMap; - } - - public Initializer resolveInitializer(final NavigablePath path) { - return initializerMap.get( path ); - } - - public void finishUpRow(final RowProcessingState rowProcessingState) { - for ( Initializer init : initializers ) { - init.finishUpRow( rowProcessingState ); - } - } - - public void startLoading(final RowProcessingState rowProcessingState) { - for ( int i = initializers.length - 1; i >= 0; i-- ) { - initializers[i].startLoading( rowProcessingState ); - } - } - - public CompletionStage initializeInstance(final ReactiveRowProcessingState rowProcessingState) { - return loop( initializers, initializer -> { - if ( initializer instanceof ReactiveInitializer ) { - return ( (ReactiveInitializer) initializer ).reactiveInitializeInstance( rowProcessingState ); - } - else { - initializer.initializeInstance( rowProcessingState ); - return voidFuture(); - } - } ); - } - - public void endLoading(final ExecutionContext executionContext) { - for ( Initializer initializer : initializers ) { - initializer.endLoading( executionContext ); - } - } - - public void resolveKeys(final RowProcessingState rowProcessingState) { - for ( Initializer init : sortedNonCollectionsFirst ) { - init.resolveKey( rowProcessingState ); - } - } - - public CompletionStage resolveInstances(final ReactiveRowProcessingState rowProcessingState) { - return loop( sortedNonCollectionsFirst, initializer -> { - if ( initializer instanceof ReactiveInitializer ) { - return ( (ReactiveInitializer) initializer ).reactiveResolveInstance( rowProcessingState ); - } - else { - initializer.resolveInstance( rowProcessingState ); - return voidFuture(); - } - } ); } public boolean hasCollectionInitializers() { @@ -103,17 +37,23 @@ public boolean hasCollectionInitializers() { } static class Builder { - private final ArrayList initializers = new ArrayList<>(); + private final ArrayList> initializers; int nonCollectionInitializersNum = 0; int resolveFirstNum = 0; - public Builder() {} + public Builder() { + initializers = new ArrayList<>(); + } + + public Builder(int size) { + initializers = new ArrayList<>( size ); + } - public void addInitializer(final Initializer initializer) { + public void addInitializer(final Initializer initializer) { initializers.add( initializer ); //in this method we perform these checks merely to learn the sizing hints, //so to not need dynamically scaling collections. - //This implies performing both checks twice but since they're cheap it's preferrable + //This implies performing both checks twice but since they're cheap it's preferable //to multiple allocations; not least this allows using arrays, which makes iteration //cheaper during the row processing - which is very hot. if ( !initializer.isCollectionInitializer() ) { @@ -124,26 +64,17 @@ public void addInitializer(final Initializer initializer) { } } - private static boolean initializeFirst(final Initializer initializer) { + private static boolean initializeFirst(final Initializer initializer) { return !( initializer instanceof EntityDelayedFetchInitializer ) && !( initializer instanceof EntitySelectFetchInitializer ); } - ReactiveInitializersList build(final Map initializerMap) { + ReactiveInitializersList build(final Map> initializerMap) { final int size = initializers.size(); - final Initializer[] sortedNonCollectionsFirst = new Initializer[size]; - final Initializer[] sortedForResolveInstance = new Initializer[size]; - int nonCollectionIdx = 0; - int collectionIdx = nonCollectionInitializersNum; + final Initializer[] sortedForResolveInstance = new Initializer[size]; int resolveFirstIdx = 0; int resolveLaterIdx = resolveFirstNum; - final Initializer[] originalSortInitializers = toArray( initializers ); - for ( Initializer initializer : originalSortInitializers ) { - if ( initializer.isCollectionInitializer() ) { - sortedNonCollectionsFirst[collectionIdx++] = initializer; - } - else { - sortedNonCollectionsFirst[nonCollectionIdx++] = initializer; - } + final Initializer[] originalSortInitializers = toArray( initializers ); + for ( Initializer initializer : originalSortInitializers ) { if ( initializeFirst( initializer ) ) { sortedForResolveInstance[resolveFirstIdx++] = initializer; } @@ -152,18 +83,11 @@ ReactiveInitializersList build(final Map initializer } } final boolean hasCollectionInitializers = ( nonCollectionInitializersNum != initializers.size() ); - return new ReactiveInitializersList( - originalSortInitializers, - sortedNonCollectionsFirst, - sortedForResolveInstance, - hasCollectionInitializers, - initializerMap - ); + return new ReactiveInitializersList( originalSortInitializers, sortedForResolveInstance, hasCollectionInitializers ); } - private Initializer[] toArray(final ArrayList initializers) { - return initializers.toArray( new Initializer[0] ); + private Initializer[] toArray(final ArrayList> initializers) { + return initializers.toArray( new Initializer[initializers.size()] ); } - } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveResultSetAccess.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveResultSetAccess.java index f99a13d53..75a265ed5 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveResultSetAccess.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveResultSetAccess.java @@ -37,6 +37,15 @@ public interface ReactiveResultSetAccess extends JdbcValuesMetadata { SessionFactoryImplementor getFactory(); void release(); + /** + * The estimate for the amount of results that can be expected for pre-sizing collections. + * May return zero or negative values if the count can not be reasonably estimated. + * @since 6.6 + */ + default int getResultCountEstimate() { + return -1; + } + default int getColumnCount() { try { return getResultSet().getMetaData().getColumnCount(); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveResultsHelper.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveResultsHelper.java index d3038621c..72d5df0c2 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveResultsHelper.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveResultsHelper.java @@ -5,27 +5,10 @@ */ package org.hibernate.reactive.sql.results.internal; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Supplier; - -import org.hibernate.LockMode; -import org.hibernate.LockOptions; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.metamodel.mapping.ModelPart; +import org.hibernate.reactive.sql.exec.spi.ReactiveValuesResultSet; import org.hibernate.reactive.sql.results.spi.ReactiveRowReader; -import org.hibernate.spi.NavigablePath; -import org.hibernate.sql.ast.spi.SqlAstCreationContext; -import org.hibernate.sql.exec.spi.ExecutionContext; -import org.hibernate.sql.results.ResultsLogger; -import org.hibernate.sql.results.graph.AssemblerCreationState; -import org.hibernate.sql.results.graph.DomainResultAssembler; -import org.hibernate.sql.results.graph.FetchParent; -import org.hibernate.sql.results.graph.FetchParentAccess; -import org.hibernate.sql.results.graph.Initializer; -import org.hibernate.sql.results.graph.InitializerProducer; -import org.hibernate.sql.results.jdbc.spi.JdbcValuesMapping; +import org.hibernate.sql.results.jdbc.spi.JdbcValuesMappingResolution; import org.hibernate.sql.results.spi.RowTransformer; /** @@ -34,126 +17,12 @@ public class ReactiveResultsHelper { public static ReactiveRowReader createRowReader( - ExecutionContext executionContext, - LockOptions lockOptions, + SessionFactoryImplementor sessionFactory, RowTransformer rowTransformer, Class transformedResultJavaType, - JdbcValuesMapping jdbcValuesMapping) { - final SessionFactoryImplementor sessionFactory = executionContext.getSession().getFactory(); - - final Map initializerMap = new LinkedHashMap<>(); - final ReactiveInitializersList.Builder initializersBuilder = new ReactiveInitializersList.Builder(); - - final List> assemblers = jdbcValuesMapping.resolveAssemblers( - creationState( executionContext, lockOptions, sessionFactory, initializerMap, initializersBuilder ) - ); - - logInitializers( initializerMap ); - - final ReactiveInitializersList initializersList = initializersBuilder.build( initializerMap ); - return new ReactiveStandardRowReader<>( assemblers, initializersList, rowTransformer, transformedResultJavaType ); - } - - private static AssemblerCreationState creationState( - ExecutionContext executionContext, - LockOptions lockOptions, - SessionFactoryImplementor sessionFactory, - Map initializerMap, - ReactiveInitializersList.Builder initializersBuilder) { - return new AssemblerCreationState() { - - @Override - public boolean isScrollResult() { - return executionContext.isScrollResult(); - } - - @Override - public LockMode determineEffectiveLockMode(String identificationVariable) { - return lockOptions.getEffectiveLockMode( identificationVariable ); - } - - @Override - public Initializer resolveInitializer( - NavigablePath navigablePath, - ModelPart fetchedModelPart, - Supplier producer) { - return resolveInitializer( - navigablePath, - fetchedModelPart, - null, - null, - (resultGraphNode, parentAccess, creationState) -> producer.get() - ); - } - - @Override - public

Initializer resolveInitializer( - P resultGraphNode, - FetchParentAccess parentAccess, - InitializerProducer

producer) { - return resolveInitializer( - resultGraphNode.getNavigablePath(), - resultGraphNode.getReferencedModePart(), - resultGraphNode, - parentAccess, - producer - ); - } - - public Initializer resolveInitializer( - NavigablePath navigablePath, - ModelPart fetchedModelPart, - T resultGraphNode, - FetchParentAccess parentAccess, - InitializerProducer producer) { - - final Initializer existing = initializerMap.get( navigablePath ); - if ( existing != null ) { - if ( fetchedModelPart.getNavigableRole().equals( - existing.getInitializedPart().getNavigableRole() ) ) { - ResultsLogger.RESULTS_MESSAGE_LOGGER.tracef( - "Returning previously-registered initializer : %s", - existing - ); - return existing; - } - } - - final Initializer initializer = producer.createInitializer( resultGraphNode, parentAccess, this ); - ResultsLogger.RESULTS_MESSAGE_LOGGER.tracef( "Registering initializer : %s", initializer ); - - initializerMap.put( navigablePath, initializer ); - initializersBuilder.addInitializer( initializer ); - - return initializer; - } - - @Override - public SqlAstCreationContext getSqlAstCreationContext() { - return sessionFactory; - } - - @Override - public ExecutionContext getExecutionContext() { - return executionContext; - } - }; - } - - private static void logInitializers(Map initializerMap) { - if ( ! ResultsLogger.RESULTS_MESSAGE_LOGGER.isDebugEnabled() ) { - return; - } - - ResultsLogger.RESULTS_MESSAGE_LOGGER.debug( "Initializer list" ); - initializerMap.forEach( (navigablePath, initializer) -> { - ResultsLogger.RESULTS_MESSAGE_LOGGER.debugf( - " %s -> %s@%s (%s)", - navigablePath, - initializer, - initializer.hashCode(), - initializer.getInitializedPart() - ); - } ); + ReactiveValuesResultSet resultSet) { + final JdbcValuesMappingResolution jdbcValuesMappingResolution = resultSet + .getValuesMapping().resolveAssemblers( sessionFactory ); + return new ReactiveStandardRowReader<>( jdbcValuesMappingResolution, rowTransformer, transformedResultJavaType ); } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveRowTransformerArrayImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveRowTransformerArrayImpl.java new file mode 100644 index 000000000..3fedd1684 --- /dev/null +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveRowTransformerArrayImpl.java @@ -0,0 +1,40 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive.sql.results.internal; + +import java.util.concurrent.CompletionStage; + +import org.hibernate.sql.results.internal.RowTransformerArrayImpl; +import org.hibernate.sql.results.spi.RowTransformer; + +import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture; + +/** + * @see org.hibernate.sql.results.internal.RowTransformerArrayImpl + */ +public class ReactiveRowTransformerArrayImpl implements RowTransformer> { + + /** + * Singleton access + * + * @see #instance() + */ + private static final RowTransformerArrayImpl INSTANCE = new RowTransformerArrayImpl(); + + public static RowTransformerArrayImpl instance() { + return INSTANCE; + } + + // I'm not sure why I need this + public static RowTransformer> asRowTransformer() { + return (RowTransformer) INSTANCE; + } + + @Override + public CompletionStage transformRow(Object[] objects) { + return completedFuture( objects ); + } +} diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveStandardRowReader.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveStandardRowReader.java index 7fda67775..87aa57103 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveStandardRowReader.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/ReactiveStandardRowReader.java @@ -5,21 +5,28 @@ */ package org.hibernate.reactive.sql.results.internal; +import java.lang.reflect.Array; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletionStage; +import org.hibernate.engine.spi.EntityKey; +import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; import org.hibernate.reactive.sql.results.graph.ReactiveDomainResultsAssembler; +import org.hibernate.reactive.sql.results.graph.ReactiveInitializer; import org.hibernate.reactive.sql.results.spi.ReactiveRowReader; import org.hibernate.sql.results.graph.DomainResultAssembler; import org.hibernate.sql.results.graph.Initializer; +import org.hibernate.sql.results.graph.InitializerData; +import org.hibernate.sql.results.jdbc.spi.JdbcValuesMappingResolution; import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions; -import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingState; import org.hibernate.sql.results.jdbc.spi.RowProcessingState; import org.hibernate.sql.results.spi.RowTransformer; import org.hibernate.type.descriptor.java.JavaType; +import static java.lang.invoke.MethodHandles.lookup; +import static org.hibernate.reactive.logging.impl.LoggerFactory.make; import static org.hibernate.reactive.util.impl.CompletionStages.loop; import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; import static org.hibernate.sql.results.LoadingLogger.LOGGER; @@ -30,23 +37,86 @@ */ public class ReactiveStandardRowReader implements ReactiveRowReader { - private final List> resultAssemblers; - private final ReactiveInitializersList initializers; + private static final Log LOG = make( Log.class, lookup() ); + + private final DomainResultAssembler[] resultAssemblers; + private final Initializer[] resultInitializers; + private final InitializerData[] resultInitializersData; + private final Initializer[] initializers; + private final InitializerData[] initializersData; + private final Initializer[] sortedForResolveInstance; + private final InitializerData[] sortedForResolveInstanceData; + private final boolean hasCollectionInitializers; private final RowTransformer rowTransformer; private final Class domainResultJavaType; + private final ComponentType componentType; + private final Class resultElementClass; + private final int assemblerCount; public ReactiveStandardRowReader( - List> resultAssemblers, - ReactiveInitializersList initializers, + JdbcValuesMappingResolution jdbcValuesMappingResolution, + RowTransformer rowTransformer, + Class domainResultJavaType) { + this( + jdbcValuesMappingResolution.getDomainResultAssemblers(), + jdbcValuesMappingResolution.getResultInitializers(), + jdbcValuesMappingResolution.getInitializers(), + jdbcValuesMappingResolution.getSortedForResolveInstance(), + jdbcValuesMappingResolution.hasCollectionInitializers(), + rowTransformer, + domainResultJavaType + ); + } + + public ReactiveStandardRowReader( + DomainResultAssembler[] resultAssemblers, + Initializer[] resultInitializers, + Initializer[] initializers, + Initializer[] sortedForResolveInitializers, + boolean hasCollectionInitializers, RowTransformer rowTransformer, Class domainResultJavaType) { this.resultAssemblers = resultAssemblers; - this.initializers = initializers; + this.resultInitializers = (Initializer[]) resultInitializers; + this.resultInitializersData = new InitializerData[resultInitializers.length]; + this.initializers = (Initializer[]) initializers; + this.initializersData = new InitializerData[initializers.length]; + this.sortedForResolveInstance = (Initializer[]) sortedForResolveInitializers; + this.sortedForResolveInstanceData = new InitializerData[sortedForResolveInstance.length]; + this.hasCollectionInitializers = hasCollectionInitializers; this.rowTransformer = rowTransformer; - this.assemblerCount = resultAssemblers.size(); this.domainResultJavaType = domainResultJavaType; + this.assemblerCount = resultAssemblers.length; + if ( domainResultJavaType == null + || domainResultJavaType == Object[].class + || domainResultJavaType == Object.class + || !domainResultJavaType.isArray() + || resultAssemblers.length == 1 + && domainResultJavaType == resultAssemblers[0].getAssembledJavaType().getJavaTypeClass() ) { + this.resultElementClass = Object.class; + this.componentType = ComponentType.OBJECT; + } + else { + this.resultElementClass = domainResultJavaType.getComponentType(); + this.componentType = ComponentType.determineComponentType( domainResultJavaType ); + } + } + + @Override + public int getInitializerCount() { + return initializers.length; + } + + @Override + public boolean hasCollectionInitializers() { + return hasCollectionInitializers; + } + + @Override + public R readRow(RowProcessingState processingState) { + throw LOG.nonReactiveMethodCall( "reactiveReadRow" ); } @Override @@ -55,76 +125,356 @@ public CompletionStage reactiveReadRow(ReactiveRowProcessingState rowProcessi return coordinateInitializers( rowProcessingState ) .thenCompose( v -> { - final Object[] resultRow = new Object[assemblerCount]; - return loop( 0, assemblerCount, i -> { - final DomainResultAssembler assembler = resultAssemblers.get( i ); - LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); - if ( assembler instanceof ReactiveDomainResultsAssembler ) { - return ( (ReactiveDomainResultsAssembler) assembler ) - .reactiveAssemble( rowProcessingState, options ) - .thenAccept( obj -> resultRow[i] = obj ); - } - resultRow[i] = assembler.assemble( rowProcessingState, options ); - return voidFuture(); - } ) - .thenApply( ignore -> { - afterRow( rowProcessingState ); - return rowTransformer.transformRow( resultRow ); - } ); + // Copied from Hibernate ORM: + // "The following is ugly, but unfortunately necessary to not hurt performance. + // This implementation was micro-benchmarked and discussed with Francesco Nigro, + // who hinted that using this style instead of the reflective Array.getLength(), Array.set() + // is easier for the JVM to optimize" + switch ( componentType ) { + case BOOLEAN: + return booleanComponent( resultAssemblers, rowProcessingState, options ); + case BYTE: + return byteComponent( resultAssemblers, rowProcessingState, options ); + case CHAR: + return charComponent( resultAssemblers, rowProcessingState, options ); + case SHORT: + return shortComponent( resultAssemblers, rowProcessingState, options ); + case INT: + return intComponent( resultAssemblers, rowProcessingState, options ); + case LONG: + return longComponent( resultAssemblers, rowProcessingState, options ); + case FLOAT: + return floatComponent( resultAssemblers, rowProcessingState, options ); + case DOUBLE: + return doubleComponent( resultAssemblers, rowProcessingState, options ); + default: + return objectComponent( resultAssemblers, rowProcessingState, options ); + } } ); } - @Override - public Class getDomainResultResultJavaType() { - return domainResultJavaType; + private CompletionStage booleanComponent( + DomainResultAssembler[] resultAssemblers, + ReactiveRowProcessingState rowProcessingState, + JdbcValuesSourceProcessingOptions options) { + final boolean[] resultRow = new boolean[resultAssemblers.length]; + return loop( 0, assemblerCount, i -> { + final DomainResultAssembler assembler = resultAssemblers[i]; + LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); + if ( assembler instanceof ReactiveDomainResultsAssembler ) { + return ( (ReactiveDomainResultsAssembler) assembler ) + .reactiveAssemble( rowProcessingState, options ) + .thenAccept( obj -> resultRow[i] = (boolean) obj ); + } + resultRow[i] = (boolean) assembler.assemble( rowProcessingState ); + return voidFuture(); + } ).thenApply( ignore -> { + afterRow( rowProcessingState ); + return (R) resultRow; + } ); + } + + private CompletionStage byteComponent( + DomainResultAssembler[] resultAssemblers, + ReactiveRowProcessingState rowProcessingState, + JdbcValuesSourceProcessingOptions options) { + final byte[] resultRow = new byte[resultAssemblers.length]; + return loop( 0, assemblerCount, i -> { + final DomainResultAssembler assembler = resultAssemblers[i]; + LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); + if ( assembler instanceof ReactiveDomainResultsAssembler ) { + return ( (ReactiveDomainResultsAssembler) assembler ) + .reactiveAssemble( rowProcessingState, options ) + .thenAccept( obj -> resultRow[i] = (byte) obj ); + } + resultRow[i] = (byte) assembler.assemble( rowProcessingState ); + return voidFuture(); + } ).thenApply( ignore -> { + afterRow( rowProcessingState ); + return (R) resultRow; + } ); + } + + private CompletionStage charComponent( + DomainResultAssembler[] resultAssemblers, + ReactiveRowProcessingState rowProcessingState, + JdbcValuesSourceProcessingOptions options) { + final char[] resultRow = new char[resultAssemblers.length]; + return loop( 0, assemblerCount, i -> { + final DomainResultAssembler assembler = resultAssemblers[i]; + LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); + if ( assembler instanceof ReactiveDomainResultsAssembler ) { + return ( (ReactiveDomainResultsAssembler) assembler ) + .reactiveAssemble( rowProcessingState, options ) + .thenAccept( obj -> resultRow[i] = (char) obj ); + } + resultRow[i] = (char) assembler.assemble( rowProcessingState ); + return voidFuture(); + } ).thenApply( ignore -> { + afterRow( rowProcessingState ); + return (R) resultRow; + } ); + } + + private CompletionStage shortComponent( + DomainResultAssembler[] resultAssemblers, + ReactiveRowProcessingState rowProcessingState, + JdbcValuesSourceProcessingOptions options) { + final short[] resultRow = new short[resultAssemblers.length]; + return loop( 0, assemblerCount, i -> { + final DomainResultAssembler assembler = resultAssemblers[i]; + LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); + if ( assembler instanceof ReactiveDomainResultsAssembler ) { + return ( (ReactiveDomainResultsAssembler) assembler ) + .reactiveAssemble( rowProcessingState, options ) + .thenAccept( obj -> resultRow[i] = (short) obj ); + } + resultRow[i] = (short) assembler.assemble( rowProcessingState ); + return voidFuture(); + } ).thenApply( ignore -> { + afterRow( rowProcessingState ); + return (R) resultRow; + } ); + } + + private CompletionStage intComponent( + DomainResultAssembler[] resultAssemblers, + ReactiveRowProcessingState rowProcessingState, + JdbcValuesSourceProcessingOptions options) { + final int[] resultRow = new int[resultAssemblers.length]; + return loop( 0, assemblerCount, i -> { + final DomainResultAssembler assembler = resultAssemblers[i]; + LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); + if ( assembler instanceof ReactiveDomainResultsAssembler ) { + return ( (ReactiveDomainResultsAssembler) assembler ) + .reactiveAssemble( rowProcessingState, options ) + .thenAccept( obj -> resultRow[i] = (int) obj ); + } + resultRow[i] = (int) assembler.assemble( rowProcessingState ); + return voidFuture(); + } ).thenApply( ignore -> { + afterRow( rowProcessingState ); + return (R) resultRow; + } ); + } + + private CompletionStage longComponent( + DomainResultAssembler[] resultAssemblers, + ReactiveRowProcessingState rowProcessingState, + JdbcValuesSourceProcessingOptions options) { + final long[] resultRow = new long[resultAssemblers.length]; + return loop( 0, assemblerCount, i -> { + final DomainResultAssembler assembler = resultAssemblers[i]; + LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); + if ( assembler instanceof ReactiveDomainResultsAssembler ) { + return ( (ReactiveDomainResultsAssembler) assembler ) + .reactiveAssemble( rowProcessingState, options ) + .thenAccept( obj -> resultRow[i] = (long) obj ); + } + resultRow[i] = (long) assembler.assemble( rowProcessingState ); + return voidFuture(); + } ).thenApply( ignore -> { + afterRow( rowProcessingState ); + return (R) resultRow; + } ); + } + + private CompletionStage floatComponent( + DomainResultAssembler[] resultAssemblers, + ReactiveRowProcessingState rowProcessingState, + JdbcValuesSourceProcessingOptions options) { + final float[] resultRow = new float[resultAssemblers.length]; + return loop( 0, assemblerCount, i -> { + final DomainResultAssembler assembler = resultAssemblers[i]; + LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); + if ( assembler instanceof ReactiveDomainResultsAssembler ) { + return ( (ReactiveDomainResultsAssembler) assembler ) + .reactiveAssemble( rowProcessingState, options ) + .thenAccept( obj -> resultRow[i] = (float) obj ); + } + resultRow[i] = (float) assembler.assemble( rowProcessingState ); + return voidFuture(); + } ).thenApply( ignore -> { + afterRow( rowProcessingState ); + return (R) resultRow; + } ); + } + + private CompletionStage doubleComponent( + DomainResultAssembler[] resultAssemblers, + ReactiveRowProcessingState rowProcessingState, + JdbcValuesSourceProcessingOptions options) { + final double[] resultRow = new double[resultAssemblers.length]; + return loop( 0, assemblerCount, i -> { + final DomainResultAssembler assembler = resultAssemblers[i]; + LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); + if ( assembler instanceof ReactiveDomainResultsAssembler ) { + return ( (ReactiveDomainResultsAssembler) assembler ) + .reactiveAssemble( rowProcessingState, options ) + .thenAccept( obj -> resultRow[i] = (double) obj ); + } + resultRow[i] = (double) assembler.assemble( rowProcessingState ); + return voidFuture(); + } ).thenApply( ignore -> { + afterRow( rowProcessingState ); + return (R) resultRow; + } ); + } + + private CompletionStage objectComponent( + DomainResultAssembler[] resultAssemblers, + ReactiveRowProcessingState rowProcessingState, + JdbcValuesSourceProcessingOptions options) { + final Object[] resultRow = (Object[]) Array.newInstance( resultElementClass, resultAssemblers.length ); + return loop( 0, assemblerCount, i -> { + final DomainResultAssembler assembler = resultAssemblers[i]; + LOGGER.debugf( "Calling top-level assembler (%s / %s) : %s", i, assemblerCount, assembler ); + if ( assembler instanceof ReactiveDomainResultsAssembler ) { + return ( (ReactiveDomainResultsAssembler) assembler ) + .reactiveAssemble( rowProcessingState, options ) + .thenAccept( obj -> resultRow[i] = obj ); + } + resultRow[i] = assembler.assemble( rowProcessingState ); + return voidFuture(); + } ).thenApply( ignore -> { + afterRow( rowProcessingState ); + return rowTransformer.transformRow( resultRow ); + } ); } @Override - public Class getResultJavaType() { - if ( resultAssemblers.size() == 1 ) { - return resultAssemblers.get( 0 ).getAssembledJavaType().getJavaTypeClass(); - } + public EntityKey resolveSingleResultEntityKey(RowProcessingState rowProcessingState) { + return null; + } - return Object[].class; + @Override + public Class getDomainResultResultJavaType() { + return domainResultJavaType; } @Override public List> getResultJavaTypes() { - List> javaTypes = new ArrayList<>( resultAssemblers.size() ); - for ( DomainResultAssembler resultAssembler : resultAssemblers ) { + List> javaTypes = new ArrayList<>( resultAssemblers.length ); + for ( DomainResultAssembler resultAssembler : resultAssemblers ) { javaTypes.add( resultAssembler.getAssembledJavaType() ); } return javaTypes; } - @Override - public List getInitializers() { - throw new UnsupportedOperationException(); + private void afterRow(RowProcessingState rowProcessingState) { + LOGGER.trace( "ReactiveStandardRowReader#afterRow" ); + finishUpRow(); } - @Override - public ReactiveInitializersList getReactiveInitializersList() { - return initializers; + private void finishUpRow() { + for ( InitializerData data : initializersData ) { + data.setState( Initializer.State.UNINITIALIZED ); + } } - @Override - public R readRow(RowProcessingState rowProcessingState, JdbcValuesSourceProcessingOptions options) { - throw LOG.nonReactiveMethodCall( "reactiveRowReader" ); + private CompletionStage coordinateInitializers(RowProcessingState rowProcessingState) { + return loop( 0, resultInitializers.length, i -> resolveKey( resultInitializers[i], resultInitializersData[i] ) ) + .thenCompose( v -> loop( 0, sortedForResolveInstance.length, i -> resolveInstance( sortedForResolveInstance[i], sortedForResolveInstanceData[i] ) ) ) + .thenCompose( v -> loop( 0, initializers.length, i -> initializeInstance( initializers[i], initializersData[i] ) ) ); } - private void afterRow(RowProcessingState rowProcessingState) { - LOGGER.trace( "ReactiveStandardRowReader#afterRow" ); - initializers.finishUpRow( rowProcessingState ); + private CompletionStage resolveKey(Initializer initializer, InitializerData initializerData) { + if ( initializer instanceof ReactiveInitializer ) { + return ( (ReactiveInitializer) initializer ).reactiveResolveKey( initializerData ); + } + initializer.resolveKey( initializerData ); + return voidFuture(); } - private CompletionStage coordinateInitializers(ReactiveRowProcessingState rowProcessingState) { - initializers.resolveKeys( rowProcessingState ); - return initializers.resolveInstances( rowProcessingState ) - .thenCompose( v -> initializers.initializeInstance( rowProcessingState ) ); + private CompletionStage resolveInstance(Initializer initializer, InitializerData initializerData) { + if ( initializerData.getState() == Initializer.State.KEY_RESOLVED ) { + if ( initializer instanceof ReactiveInitializer ) { + return ( (ReactiveInitializer) initializer ).reactiveResolveInstance( initializerData ); + } + initializer.resolveInstance( initializerData ); + } + return voidFuture(); + } + + private CompletionStage initializeInstance(Initializer initializer, InitializerData initializerData) { + if ( initializerData.getState() == Initializer.State.RESOLVED ) { + if ( initializer instanceof ReactiveInitializer ) { + return ( (ReactiveInitializer) initializer ).reactiveInitializeInstance( initializerData ); + } + initializer.initializeInstance( initializerData ); + } + return voidFuture(); } @Override - public void finishUp(JdbcValuesSourceProcessingState processingState) { - initializers.endLoading( processingState.getExecutionContext() ); + public void startLoading(RowProcessingState processingState) { + for ( int i = 0; i < resultInitializers.length; i++ ) { + final Initializer initializer = resultInitializers[i]; + initializer.startLoading( processingState ); + resultInitializersData[i] = initializer.getData( processingState ); + } + for ( int i = 0; i < sortedForResolveInstance.length; i++ ) { + sortedForResolveInstanceData[i] = sortedForResolveInstance[i].getData( processingState ); + } + for ( int i = 0; i < initializers.length; i++ ) { + initializersData[i] = initializers[i].getData( processingState ); + } + } + + @Override + public void finishUp(RowProcessingState rowProcessingState) { + for ( int i = 0; i < initializers.length; i++ ) { + initializers[i].endLoading( initializersData[i] ); + } + } + + enum ComponentType { + BOOLEAN( boolean.class ), + BYTE( byte.class ), + SHORT( short.class ), + CHAR( char.class ), + INT( int.class ), + LONG( long.class ), + FLOAT( float.class ), + DOUBLE( double.class ), + OBJECT( Object.class ); + + private final Class componentType; + + ComponentType(Class componentType) { + this.componentType = componentType; + } + + public static ComponentType determineComponentType(Class resultType) { + if ( resultType == boolean[].class ) { + return BOOLEAN; + } + if ( resultType == byte[].class ) { + return BYTE; + } + if ( resultType == short[].class ) { + return SHORT; + } + if ( resultType == char[].class ) { + return CHAR; + } + if ( resultType == int[].class ) { + return INT; + } + if ( resultType == long[].class ) { + return LONG; + } + if ( resultType == float[].class ) { + return FLOAT; + } + if ( resultType == double[].class ) { + return DOUBLE; + } + return OBJECT; + } + + public Class getComponentType() { + return componentType; + } } } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/domain/ReactiveCircularFetchImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/domain/ReactiveCircularFetchImpl.java index d42cdff6c..1fa443ddf 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/domain/ReactiveCircularFetchImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/internal/domain/ReactiveCircularFetchImpl.java @@ -7,11 +7,13 @@ import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.reactive.sql.results.graph.entity.internal.ReactiveEntityDelayedFetchInitializer; import org.hibernate.reactive.sql.results.graph.entity.internal.ReactiveEntitySelectFetchInitializerBuilder; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.results.graph.AssemblerCreationState; import org.hibernate.sql.results.graph.DomainResult; -import org.hibernate.sql.results.graph.FetchParentAccess; +import org.hibernate.sql.results.graph.InitializerParent; +import org.hibernate.sql.results.graph.basic.BasicFetch; import org.hibernate.sql.results.graph.entity.EntityInitializer; import org.hibernate.sql.results.internal.domain.CircularFetchImpl; @@ -24,8 +26,8 @@ public ReactiveCircularFetchImpl(CircularFetchImpl original) { } @Override - protected EntityInitializer buildEntitySelectFetchInitializer( - FetchParentAccess parentAccess, + protected EntityInitializer buildEntitySelectFetchInitializer( + InitializerParent parent, ToOneAttributeMapping fetchable, EntityPersister entityPersister, DomainResult keyResult, @@ -33,12 +35,33 @@ protected EntityInitializer buildEntitySelectFetchInitializer( boolean selectByUniqueKey, AssemblerCreationState creationState) { return ReactiveEntitySelectFetchInitializerBuilder.createInitializer( - parentAccess, + parent, fetchable, entityPersister, keyResult, navigablePath, selectByUniqueKey, + false, + creationState + ); + } + + @Override + protected EntityInitializer buildEntityDelayedFetchInitializer( + InitializerParent parent, + NavigablePath referencedPath, + ToOneAttributeMapping fetchable, + boolean selectByUniqueKey, + DomainResult keyResult, + BasicFetch discriminatorFetch, + AssemblerCreationState creationState) { + return new ReactiveEntityDelayedFetchInitializer( + parent, + referencedPath, + fetchable, + selectByUniqueKey, + keyResult, + discriminatorFetch, creationState ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveListResultsConsumer.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveListResultsConsumer.java index 5a563c093..7c23bc739 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveListResultsConsumer.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveListResultsConsumer.java @@ -19,9 +19,9 @@ import org.hibernate.query.spi.QueryOptions; import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; import org.hibernate.reactive.sql.exec.spi.ReactiveValuesResultSet; +import org.hibernate.reactive.util.impl.CompletionStages; import org.hibernate.sql.results.jdbc.internal.JdbcValuesSourceProcessingStateStandardImpl; import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions; -import org.hibernate.sql.results.spi.LoadContexts; import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.java.spi.EntityJavaType; import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry; @@ -34,7 +34,6 @@ import static org.hibernate.reactive.util.impl.CompletionStages.whileLoop; /** - * * @see org.hibernate.sql.results.spi.ListResultsConsumer */ public class ReactiveListResultsConsumer implements ReactiveResultsConsumer, R> { @@ -45,7 +44,7 @@ public class ReactiveListResultsConsumer implements ReactiveResultsConsumer DE_DUP_CONSUMER = new ReactiveListResultsConsumer<>( FILTER ); private static final ReactiveListResultsConsumer ERROR_DUP_CONSUMER = new ReactiveListResultsConsumer<>( ASSERT ); - private static void validateUniqueResult(Boolean unique) { + private static boolean validateUniqueResult(Boolean unique) { if ( !unique ) { throw new HibernateException( String.format( Locale.ROOT, @@ -53,25 +52,7 @@ private static void validateUniqueResult(Boolean unique) { ASSERT ) ); } - } - - private static class RegistrationHandler { - - private final LoadContexts contexts; - private final JdbcValuesSourceProcessingStateStandardImpl state; - - private RegistrationHandler(LoadContexts contexts, JdbcValuesSourceProcessingStateStandardImpl state) { - this.contexts = contexts; - this.state = state; - } - - public void register() { - contexts.register( state ); - } - - public void deregister() { - contexts.deregister( state ); - } + return true; } @Override @@ -101,65 +82,89 @@ public CompletionStage> consume( ? new EntityResult<>( domainResultJavaType ) : new Results<>( domainResultJavaType ); - Supplier> addToResultsSupplier = addToResultsSupplier( results, rowReader, rowProcessingState, processingOptions, isEntityResultType ); - final int[] readRows = { 0 }; + Supplier> addToResultsSupplier = addToResultsSupplier( + results, + rowReader, + rowProcessingState, + processingOptions, + isEntityResultType + ); + final int[] readRows = {0}; return whileLoop( () -> rowProcessingState.next() - .thenCompose( hasNext -> { - if ( hasNext ) { - return addToResultsSupplier.get() - .thenApply( unused -> { - rowProcessingState.finishRowProcessing(); - readRows[0]++; - return true; - } ); - - } - return falseFuture(); - } ) - ) - .thenApply( v -> finishUp( results, jdbcValuesSourceProcessingState, rowReader, persistenceContext, queryOptions, readRows[0] ) ) - .handle( (list, ex) -> { - end( jdbcValues, session, jdbcValuesSourceProcessingState, rowReader, persistenceContext, ex ); - return list; - } ); + .thenCompose( hasNext -> { + if ( hasNext ) { + return addToResultsSupplier.get() + .thenApply( added -> { + rowProcessingState.finishRowProcessing( added ); + readRows[0]++; + return true; + } ); + + } + return falseFuture(); + } ) ) + .thenApply( v -> finishUp( rowReader, rowProcessingState, jdbcValuesSourceProcessingState, results, readRows, queryOptions ) ) + .handle( CompletionStages::handle ) + .thenCompose( handler -> { + end( jdbcValues, session, jdbcValuesSourceProcessingState, persistenceContext, handler.getThrowable() ); + return handler.getResultAsCompletionStage(); + } ); + } + + private List finishUp( + ReactiveRowReader rowReader, + ReactiveRowProcessingState rowProcessingState, + JdbcValuesSourceProcessingStateStandardImpl jdbcValuesSourceProcessingState, + Results results, int[] readRows, QueryOptions queryOptions) { + rowReader.finishUp( rowProcessingState ); + jdbcValuesSourceProcessingState.finishUp( readRows[0] > 1 ); + + final ResultListTransformer resultListTransformer = (ResultListTransformer) queryOptions.getResultListTransformer(); + return resultListTransformer != null + ? resultListTransformer.transformList( results.getResults() ) + : results.getResults(); } - private Supplier> addToResultsSupplier( + /** + * The boolean in the CompletionStage is true if the element has been added to the results + */ + private Supplier> addToResultsSupplier( ReactiveListResultsConsumer.Results results, ReactiveRowReader rowReader, ReactiveRowProcessingState rowProcessingState, JdbcValuesSourceProcessingOptions processingOptions, boolean isEntityResultType) { if ( this.uniqueSemantic == FILTER - || this.uniqueSemantic == ASSERT && rowProcessingState.hasCollectionInitializers() + || this.uniqueSemantic == ASSERT && rowReader.hasCollectionInitializers() || this.uniqueSemantic == ALLOW && isEntityResultType ) { return () -> rowReader .reactiveReadRow( rowProcessingState, processingOptions ) - .thenAccept( results::addUnique ); + .thenApply( results::addUnique ); } if ( this.uniqueSemantic == ASSERT ) { return () -> rowReader .reactiveReadRow( rowProcessingState, processingOptions ) .thenApply( results::addUnique ) - .thenAccept( ReactiveListResultsConsumer::validateUniqueResult ); + .thenApply( ReactiveListResultsConsumer::validateUniqueResult ); } return () -> rowReader .reactiveReadRow( rowProcessingState, processingOptions ) - .thenAccept( results::add ); + .thenApply( results::add ); } + private void end( ReactiveValuesResultSet jdbcValues, SharedSessionContractImplementor session, JdbcValuesSourceProcessingStateStandardImpl jdbcValuesSourceProcessingState, - ReactiveRowReader rowReader, PersistenceContext persistenceContext, Throwable ex) { try { - rowReader.finishUp( jdbcValuesSourceProcessingState ); + jdbcValues.finishUp( session ); persistenceContext.afterLoad(); + persistenceContext.getLoadContexts().deregister( jdbcValuesSourceProcessingState ); persistenceContext.initializeNonLazyCollections(); } catch (Throwable e) { @@ -174,26 +179,6 @@ private void end( } } - private List finishUp( - Results results, - JdbcValuesSourceProcessingStateStandardImpl jdbcValuesSourceProcessingState, - ReactiveRowReader rowReader, PersistenceContext persistenceContext, - QueryOptions queryOptions, - int readRows) { - try { - rowReader.finishUp( jdbcValuesSourceProcessingState ); - jdbcValuesSourceProcessingState.finishUp( readRows > 0 ); - } - finally { - persistenceContext.getLoadContexts().deregister( jdbcValuesSourceProcessingState ); - } - - final ResultListTransformer resultListTransformer = (ResultListTransformer) queryOptions.getResultListTransformer(); - return resultListTransformer != null - ? resultListTransformer.transformList( results.getResults() ) - : results.getResults(); - } - @SuppressWarnings("unchecked") public static ReactiveListResultsConsumer instance(ReactiveListResultsConsumer.UniqueSemantic uniqueSemantic) { switch ( uniqueSemantic ) { @@ -298,8 +283,9 @@ public boolean addUnique(R result) { return true; } - public void add(R result) { + public boolean add(R result) { results.add( result ); + return true; } public List getResults() { diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveRowReader.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveRowReader.java index 9e0df6d04..fcf8bb760 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveRowReader.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveRowReader.java @@ -5,27 +5,13 @@ */ package org.hibernate.reactive.sql.results.spi; -import java.lang.invoke.MethodHandles; import java.util.concurrent.CompletionStage; -import org.hibernate.reactive.logging.impl.Log; -import org.hibernate.reactive.logging.impl.LoggerFactory; import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState; -import org.hibernate.reactive.sql.results.internal.ReactiveInitializersList; -import org.hibernate.sql.results.internal.InitializersList; import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions; import org.hibernate.sql.results.spi.RowReader; public interface ReactiveRowReader extends RowReader { - Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() ); - CompletionStage reactiveReadRow(ReactiveRowProcessingState processingState, JdbcValuesSourceProcessingOptions options); - - @Override - default InitializersList getInitializersList() { - throw LOG.nonReactiveMethodCall( "getReactiveInitializersList" ); - } - - ReactiveInitializersList getReactiveInitializersList(); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveSingleResultConsumer.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveSingleResultConsumer.java index ad3d17853..814db1e4b 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveSingleResultConsumer.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/sql/results/spi/ReactiveSingleResultConsumer.java @@ -25,13 +25,13 @@ public CompletionStage consume( JdbcValuesSourceProcessingStateStandardImpl jdbcValuesSourceProcessingState, ReactiveRowProcessingState rowProcessingState, ReactiveRowReader rowReader) { - rowReader.getReactiveInitializersList().startLoading( rowProcessingState ); + rowReader.startLoading( rowProcessingState ); return rowProcessingState.next() .thenCompose( hasNext -> rowReader .reactiveReadRow( rowProcessingState, processingOptions ) .thenApply( result -> { rowProcessingState.finishRowProcessing( true ); - rowReader.finishUp( jdbcValuesSourceProcessingState ); + rowReader.finishUp( rowProcessingState ); jdbcValuesSourceProcessingState.finishUp( false ); return result; } ) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveArrayJdbcTypeConstructor.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveArrayJdbcTypeConstructor.java index b8b029fbd..f0a26dccc 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveArrayJdbcTypeConstructor.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveArrayJdbcTypeConstructor.java @@ -33,7 +33,7 @@ public JdbcType resolveType( if ( realDialect instanceof OracleDialect ) { String typeName = columnTypeInformation == null ? null : columnTypeInformation.getTypeName(); if ( typeName == null || typeName.isBlank() ) { - typeName = ReactiveOracleArrayJdbcType.getTypeName( elementType.getJavaTypeDescriptor(), dialect ); + typeName = ReactiveOracleArrayJdbcType.getTypeName( elementType, dialect ); } return new ReactiveOracleArrayJdbcType( elementType.getJdbcType(), typeName ); } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveOracleArrayJdbcType.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveOracleArrayJdbcType.java index 8cb2d9cd5..52a9d9206 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveOracleArrayJdbcType.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/type/descriptor/jdbc/ReactiveOracleArrayJdbcType.java @@ -15,12 +15,17 @@ import org.hibernate.dialect.Dialect; import org.hibernate.dialect.OracleArrayJdbcType; import org.hibernate.reactive.adaptor.impl.ArrayAdaptor; +import org.hibernate.type.BasicType; import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.converter.spi.BasicValueConverter; +import org.hibernate.type.descriptor.converter.spi.JpaAttributeConverter; import org.hibernate.type.descriptor.java.BasicPluralJavaType; import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.jdbc.ArrayJdbcType; import org.hibernate.type.descriptor.jdbc.BasicBinder; import org.hibernate.type.descriptor.jdbc.JdbcType; +import org.hibernate.type.descriptor.jdbc.StructJdbcType; import static java.sql.Types.ARRAY; @@ -29,8 +34,11 @@ */ public class ReactiveOracleArrayJdbcType extends OracleArrayJdbcType { + private final String upperTypeName; + public ReactiveOracleArrayJdbcType(JdbcType elementJdbcType, String typeName) { super( elementJdbcType, typeName ); + this.upperTypeName = typeName == null ? null : typeName.toUpperCase( Locale.ROOT ); } public static String getTypeName(WrapperOptions options, BasicPluralJavaType containerJavaType) { @@ -52,8 +60,10 @@ public ValueBinder getBinder(final JavaType javaTypeDescriptor) { final BasicPluralJavaType containerJavaType = (BasicPluralJavaType) javaTypeDescriptor; return new BasicBinder<>( javaTypeDescriptor, this ) { private String typeName(WrapperOptions options) { - String typeName = getTypeName() == null ? getTypeName( options, containerJavaType ) : getTypeName(); - return typeName.toUpperCase( Locale.ROOT ); + return ( upperTypeName == null + ? getTypeName( options, (BasicPluralJavaType) getJavaType(), (ArrayJdbcType) getJdbcType() ).toUpperCase( Locale.ROOT ) + : upperTypeName + ); } @Override @@ -93,4 +103,71 @@ private ArrayAdaptor getArray(X value, BasicPluralJavaType containerJavaType, } }; } + + /* + * FIXME: We should change the scope of these methods in ORM: see OracleArrayJdbcType#getTypeName + */ + + static String getTypeName(WrapperOptions options, BasicPluralJavaType containerJavaType, ArrayJdbcType arrayJdbcType) { + Dialect dialect = options.getSessionFactory().getJdbcServices().getDialect(); + return getTypeName( containerJavaType.getElementJavaType(), arrayJdbcType.getElementJdbcType(), dialect ); + } + + static String getTypeName(BasicType elementType, Dialect dialect) { + final BasicValueConverter converter = elementType.getValueConverter(); + if ( converter != null ) { + final String simpleName; + if ( converter instanceof JpaAttributeConverter ) { + simpleName = ( (JpaAttributeConverter) converter ).getConverterJavaType() + .getJavaTypeClass() + .getSimpleName(); + } + else { + simpleName = converter.getClass().getSimpleName(); + } + return dialect.getArrayTypeName( + simpleName, + null, // not needed by OracleDialect.getArrayTypeName() + null // not needed by OracleDialect.getArrayTypeName() + ); + } + return getTypeName( elementType.getJavaTypeDescriptor(), elementType.getJdbcType(), dialect ); + } + + static String getTypeName(JavaType elementJavaType, JdbcType elementJdbcType, Dialect dialect) { + final String simpleName; + if ( elementJavaType.getJavaTypeClass().isArray() ) { + simpleName = dialect.getArrayTypeName( + elementJavaType.getJavaTypeClass().getComponentType().getSimpleName(), + null, // not needed by OracleDialect.getArrayTypeName() + null // not needed by OracleDialect.getArrayTypeName() + ); + } + else if ( elementJdbcType instanceof StructJdbcType ) { + simpleName = ( (StructJdbcType) elementJdbcType ).getStructTypeName(); + } + else { + final Class preferredJavaTypeClass = elementJdbcType.getPreferredJavaTypeClass( null ); + if ( preferredJavaTypeClass == elementJavaType.getJavaTypeClass() ) { + simpleName = elementJavaType.getJavaTypeClass().getSimpleName(); + } + else { + if ( preferredJavaTypeClass.isArray() ) { + simpleName = elementJavaType.getJavaTypeClass().getSimpleName() + dialect.getArrayTypeName( + preferredJavaTypeClass.getComponentType().getSimpleName(), + null, + null + ); + } + else { + simpleName = elementJavaType.getJavaTypeClass().getSimpleName() + preferredJavaTypeClass.getSimpleName(); + } + } + } + return dialect.getArrayTypeName( + simpleName, + null, // not needed by OracleDialect.getArrayTypeName() + null // not needed by OracleDialect.getArrayTypeName() + ); + } } diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EagerOneToOneAssociationTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EagerOneToOneAssociationTest.java index c45f8a87b..0b7e3b41e 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EagerOneToOneAssociationTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/EagerOneToOneAssociationTest.java @@ -8,8 +8,10 @@ import java.util.Collection; import java.util.List; import java.util.Objects; +import java.util.concurrent.CompletionStage; + +import org.hibernate.TransientObjectException; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import io.vertx.junit5.Timeout; @@ -22,14 +24,32 @@ import jakarta.persistence.Table; import static java.util.concurrent.TimeUnit.MINUTES; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hibernate.reactive.testing.ReactiveAssertions.assertThrown; +import static org.hibernate.reactive.util.impl.CompletionStages.loop; @Timeout(value = 10, timeUnit = MINUTES) - public class EagerOneToOneAssociationTest extends BaseReactiveTest { @Override protected Collection> annotatedEntities() { - return List.of( Book.class, Author.class ); + return List.of( Author.class, Book.class ); + } + + @Override + public CompletionStage deleteEntities(Class... entities) { + return getSessionFactory() + .withTransaction( s -> s + .createSelectionQuery( "from Book", Book.class ) + .getResultList() + .thenCompose( books -> loop( books, book -> { + Author author = book.author; + book.author = null; + author.mostPopularBook = null; + return s.remove( book, author ); + } ) + ) + ); } @Test @@ -39,16 +59,48 @@ public void testPersist(VertxTestContext context) { mostPopularBook.setAuthor( author ); author.setMostPopularBook( mostPopularBook ); - test( - context, - openSession() - .thenCompose( s -> s.persist( mostPopularBook ) - .thenCompose( v -> s.persist( author ) ) - .thenCompose( v -> s.flush() ) - ) - .thenCompose( v -> openSession() ) - .thenCompose( s -> s.find( Book.class, 5 ) ) - .thenAccept( Assertions::assertNotNull) + test( context, openSession() + .thenCompose( s -> s + .persist( mostPopularBook ) + .thenCompose( v -> s.persist( author ) ) + .thenCompose( v -> s.flush() ) + ) + .thenCompose( v -> openSession() ) + .thenCompose( s -> s.find( Book.class, 5 ) ) + .thenAccept( book -> { + assertThat( book ).isEqualTo( mostPopularBook ); + assertThat( book.getAuthor() ).isEqualTo( author ); + } ) + ); + } + + @Test + public void testTransientException(VertxTestContext context) { + final Book yellowface = new Book( 7, "Yellowface" ); + final Author kuang = new Author( 19, "R.F. Kuang" ); + yellowface.setAuthor( kuang ); + kuang.setMostPopularBook( yellowface ); + + test( context, assertThrown( TransientObjectException.class, openSession() + .thenCompose( s -> s + .persist( yellowface ) + .thenCompose( v -> s.persist( kuang ) ) + .thenCompose( v -> s.flush() ) + ) + .thenCompose( v -> openSession() ) + .thenCompose( s -> s + .createSelectionQuery( "from Book", Book.class ) + .getResultList() + .thenCompose( books -> s.remove( books.toArray() ) ) + // This query should force an auto-flush. Because we have deleted the book but not the associated author + // it should cause a TransientObjectException. Note that this validation has been added in 6.6, and the same test + // wasn't throwing any exception with ORM 6.5 + .thenCompose( v -> s + .createSelectionQuery( "from Author", Author.class ) + .getResultList() + ) + ) + ) ); } diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OrderTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OrderTest.java index 097db58ff..48b73829f 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OrderTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OrderTest.java @@ -19,6 +19,7 @@ import io.vertx.junit5.VertxTestContext; import jakarta.persistence.Entity; import jakarta.persistence.Id; +import jakarta.persistence.Table; import jakarta.persistence.metamodel.SingularAttribute; import static java.util.concurrent.TimeUnit.MINUTES; @@ -302,6 +303,7 @@ private void assertOrderByBookArray(List resultList, Book... expectedB } @Entity(name = "Book") + @Table(name = "OrderTest_Book" ) static class Book { @Id String isbn; diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/SoftDeleteTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/SoftDeleteTest.java index c25b25b1e..4ba2e844e 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/SoftDeleteTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/SoftDeleteTest.java @@ -30,6 +30,7 @@ import jakarta.persistence.criteria.CriteriaDelete; import jakarta.persistence.criteria.Root; +import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.DB2; import static org.hibernate.reactive.containers.DatabaseConfiguration.dbType; @@ -103,16 +104,10 @@ public void testDeletedStrategyWithYesNoConverter(VertxTestContext context) { @Test public void testDefaults(VertxTestContext context) { - Predicate deleted = obj -> { - switch ( dbType() ) { - case DB2: - return ( (short) obj ) == 1; - case ORACLE: - return ( (Number) obj ).intValue() == 1; - default: - return (boolean) obj; - } - }; + Predicate deleted = obj -> dbType() == DB2 + ? ( (short) obj ) == 1 + : (boolean) obj; + testSoftDelete( context, deleted, "deleted", ImplicitEntity.class, implicitEntities, () -> getMutinySessionFactory().withTransaction( s -> s .remove( s.getReference( ImplicitEntity.class, implicitEntities[0].getId() ) ) @@ -122,16 +117,10 @@ public void testDefaults(VertxTestContext context) { @Test public void testDeletionWithHQLQuery(VertxTestContext context) { - Predicate deleted = obj -> { - switch ( dbType() ) { - case DB2: - return ( (short) obj ) == 1; - case ORACLE: - return ( (Number) obj ).intValue() == 1; - default: - return (boolean) obj; - } - }; + Predicate deleted = obj -> requireNonNull( dbType() ) == DB2 + ? ( (short) obj ) == 1 + : (boolean) obj; + testSoftDelete( context, deleted, "deleted", ImplicitEntity.class, implicitEntities, () -> getMutinySessionFactory().withTransaction( s -> s .createMutationQuery( "delete from ImplicitEntity where name = :name" ) @@ -143,16 +132,10 @@ public void testDeletionWithHQLQuery(VertxTestContext context) { @Test public void testDeletionWithCriteria(VertxTestContext context) { - Predicate deleted = obj -> { - switch ( dbType() ) { - case DB2: - return ( (short) obj ) == 1; - case ORACLE: - return ( (Number) obj ).intValue() == 1; - default: - return (boolean) obj; - } - }; + Predicate deleted = obj -> dbType() == DB2 + ? ( (short) obj ) == 1 + : (boolean) obj; + testSoftDelete( context, deleted, "deleted", ImplicitEntity.class, implicitEntities, () -> getMutinySessionFactory().withTransaction( s -> { CriteriaBuilder cb = getSessionFactory().getCriteriaBuilder(); diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/UriConfigTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/UriConfigTest.java index 874517f34..788adccea 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/UriConfigTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/UriConfigTest.java @@ -5,20 +5,11 @@ */ package org.hibernate.reactive; -import static java.util.concurrent.TimeUnit.MINUTES; -import static org.hibernate.reactive.containers.DatabaseConfiguration.dbType; +import java.net.URI; import org.hibernate.cfg.Configuration; -import org.hibernate.cfg.Environment; -import org.hibernate.dialect.CockroachDialect; -import org.hibernate.dialect.DB2Dialect; -import org.hibernate.dialect.Dialect; -import org.hibernate.dialect.MariaDBDialect; -import org.hibernate.dialect.MySQLDialect; -import org.hibernate.dialect.OracleDialect; -import org.hibernate.dialect.PostgreSQLDialect; -import org.hibernate.dialect.SQLServerDialect; import org.hibernate.reactive.containers.DatabaseConfiguration; +import org.hibernate.reactive.pool.impl.SqlClientPoolConfiguration; import org.hibernate.reactive.provider.Settings; import org.junit.jupiter.api.Assertions; @@ -26,6 +17,11 @@ import io.vertx.junit5.Timeout; import io.vertx.junit5.VertxTestContext; +import io.vertx.sqlclient.PoolOptions; +import io.vertx.sqlclient.SqlConnectOptions; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.hibernate.reactive.containers.DatabaseConfiguration.dbType; @Timeout(value = 10, timeUnit = MINUTES) @@ -33,20 +29,7 @@ public class UriConfigTest extends BaseReactiveTest { @Override protected Configuration constructConfiguration() { - Class dialect; - switch ( dbType() ) { - case POSTGRESQL: dialect = PostgreSQLDialect.class; break; - case COCKROACHDB: dialect = CockroachDialect.class; break; - case MYSQL: dialect = MySQLDialect.class; break; - case MARIA: dialect = MariaDBDialect.class; break; - case SQLSERVER: dialect = SQLServerDialect.class; break; - case DB2: dialect = DB2Dialect.class; break; - case ORACLE: dialect = OracleDialect.class; break; - default: throw new IllegalArgumentException( "Database not recognized: " + dbType().name() ); - } - Configuration configuration = super.constructConfiguration(); - configuration.setProperty( Environment.DIALECT, dialect.getName() ); configuration.setProperty( Settings.URL, DatabaseConfiguration.getUri() ); configuration.setProperty( Settings.SQL_CLIENT_POOL_CONFIG, UriPoolConfiguration.class.getName() ); return configuration; @@ -77,4 +60,30 @@ private String selectQuery() { throw new IllegalArgumentException( "Database not recognized: " + dbType().name() ); } } + + /** + * This class is used by {@link UriConfigTest} to test that one can use a different implementation of + * {@link SqlClientPoolConfiguration}. + *

+ * But, it also test {@link SqlConnectOptions#fromUri(String)} for each database. + *

+ */ + public static class UriPoolConfiguration implements SqlClientPoolConfiguration { + @Override + public PoolOptions poolOptions() { + return new PoolOptions(); + } + + @Override + public SqlConnectOptions connectOptions(URI uri) { + // For CockroachDB we use the PostgreSQL Vert.x client + String uriString = uri.toString().replaceAll( "^cockroach(db)?:", "postgres:" ); + if ( uriString.startsWith( "sqlserver" ) ) { + // Testscontainer adds encrypt=false to the url. The problem is that it uses the JDBC syntax that's + // different from the supported one by the Vert.x driver. + uriString = uriString.replaceAll( ";[e|E]ncrypt=false", "?encrypt=false" ); + } + return SqlConnectOptions.fromUri( uriString ); + } + } } diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/UriPoolConfiguration.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/UriPoolConfiguration.java deleted file mode 100644 index 5ac7c404e..000000000 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/UriPoolConfiguration.java +++ /dev/null @@ -1,39 +0,0 @@ -/* 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.net.URI; - -import org.hibernate.reactive.pool.impl.SqlClientPoolConfiguration; - -import io.vertx.sqlclient.PoolOptions; -import io.vertx.sqlclient.SqlConnectOptions; - -/** - * This class is used by {@link UriConfigTest} to test that one can use a different implementation of - * {@link SqlClientPoolConfiguration}. - *

- * But, in reality, it also checks {@link SqlConnectOptions#fromUri(String)} works for each database. - *

- */ -public class UriPoolConfiguration implements SqlClientPoolConfiguration { - @Override - public PoolOptions poolOptions() { - return new PoolOptions(); - } - - @Override - public SqlConnectOptions connectOptions(URI uri) { - // For CockroachDB we use the PostgreSQL Vert.x client - String uriString = uri.toString().replaceAll( "^cockroach(db)?:", "postgres:" ); - if ( uriString.startsWith( "sqlserver" ) ) { - // Testscontainer adds encrypt=false to the url. The problem is that it uses the JDBC syntax that's - // different from the supported one by the Vert.x driver. - uriString = uriString.replaceAll( ";encrypt=false", "?encrypt=false" ); - } - return SqlConnectOptions.fromUri( uriString ); - } -} diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/configuration/ReactiveConnectionPoolTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/configuration/ReactiveConnectionPoolTest.java index babae4bd4..df28ce62a 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/configuration/ReactiveConnectionPoolTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/configuration/ReactiveConnectionPoolTest.java @@ -14,13 +14,13 @@ import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.jdbc.spi.SqlExceptionHelper; import org.hibernate.engine.jdbc.spi.SqlStatementLogger; +import org.hibernate.reactive.annotations.EnabledFor; import org.hibernate.reactive.containers.DatabaseConfiguration; import org.hibernate.reactive.pool.ReactiveConnectionPool; import org.hibernate.reactive.pool.impl.DefaultSqlClientPool; import org.hibernate.reactive.pool.impl.DefaultSqlClientPoolConfiguration; import org.hibernate.reactive.pool.impl.SqlClientPoolConfiguration; import org.hibernate.reactive.testing.TestingRegistryExtension; -import org.hibernate.reactive.annotations.EnabledFor; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/OracleDatabase.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/OracleDatabase.java index 6f157192e..9ad2f8136 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/OracleDatabase.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/containers/OracleDatabase.java @@ -44,10 +44,9 @@ class OracleDatabase implements TestableDatabase { static { { - expectedDBTypeForClass.put( boolean.class, "NUMBER" ); - expectedDBTypeForClass.put( Boolean.class, "NUMBER" ); + expectedDBTypeForClass.put( boolean.class, "BOOLEAN" ); + expectedDBTypeForClass.put( Boolean.class, "BOOLEAN" ); - // FIXME: [ORM-6] Check if we need alternatives expectedDBTypeForClass.put( NumericBooleanConverter.class, "NUMBER" ); expectedDBTypeForClass.put( YesNoConverter.class, "CHAR" ); expectedDBTypeForClass.put( TrueFalseConverter.class, "CHAR" ); diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaUpdateOracleTestBase.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaUpdateOracleTestBase.java index 2e26d2751..4f46641a1 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaUpdateOracleTestBase.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/schema/SchemaUpdateOracleTestBase.java @@ -10,8 +10,8 @@ import org.hibernate.cfg.Configuration; import org.hibernate.reactive.BaseReactiveTest; -import org.hibernate.reactive.provider.Settings; import org.hibernate.reactive.annotations.EnabledFor; +import org.hibernate.reactive.provider.Settings; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach;