Repository: hibernate/hibernate-reactive (java)

Issue:
StatelessSession.get() accepting multiple ids
We already have a `Session.find(Class, Object...)` which is the analog of `Session.findMultiple()` in ORM.

We are missing `StatelessSession.get(Class, Object...)` as an analog of ``StatelessSession.findMultiple()`.

This should be _very_ easy to add.

Pull request:
add StatelessSession.get() accepting multiple ids
and add missing overloads of `Mutiny.StatelessSession.createQuery()`

Fix #2122.

Also add tests for `Mutiny.StatelessSession` which were missing

Patch:
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 062f0374..977179c7 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
@@ -63,7 +63,7 @@ public CompletionStage<Void> reactiveExecute() throws HibernateException {
 			final ReactiveEntityPersister reactivePersister = (ReactiveEntityPersister) persister;
 			final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
 			return stage
-					.thenCompose( v -> reactivePersister.insertReactive( id, getState(), instance, session, false ) )
+					.thenCompose( v -> reactivePersister.insertReactive( id, getState(), instance, session ) )
 					.thenCompose( generatedValues -> {
 						final EntityEntry entry = persistenceContext.getEntry( instance );
 						if ( entry == null ) {
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/Mutiny.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/Mutiny.java
index c561a412..e6cdab9d 100644
--- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/Mutiny.java
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/Mutiny.java
@@ -40,6 +40,7 @@
 import jakarta.persistence.CacheRetrieveMode;
 import jakarta.persistence.CacheStoreMode;
 import jakarta.persistence.EntityGraph;
+import jakarta.persistence.FindOption;
 import jakarta.persistence.FlushModeType;
 import jakarta.persistence.LockModeType;
 import jakarta.persistence.Parameter;
@@ -617,6 +618,8 @@ default <T> Uni<T> find(Class<T> entityClass, Object id, LockModeType lockModeTy
 		 * @param ids the identifiers
 		 *
 		 * @return a list of persistent instances and nulls via a {@code Uni}
+		 *
+		 * @see org.hibernate.Session#findMultiple(Class, List, FindOption...)
 		 */
 		<T> Uni<List<T>> find(Class<T> entityClass, Object... ids);
 
@@ -1569,6 +1572,18 @@ interface StatelessSession extends Closeable {
 		 */
 		<T> Uni<T> get(Class<T> entityClass, Object id);
 
+		/**
+		 * Retrieve multiple rows.
+		 *
+		 * @param entityClass The class of the entity to retrieve
+		 * @param ids The ids of the entities to retrieve
+		 *
+		 * @return a list of detached entity instances, via a {@code Uni}
+		 *
+		 * @see org.hibernate.StatelessSession#getMultiple(Class, List)
+		 */
+		<T> Uni<List<T>> get(Class<T> entityClass, Object... ids);
+
 		/**
 		 * Retrieve a row, obtaining the specified lock mode.
 		 *
@@ -1737,6 +1752,24 @@ default <T> Uni<T> get(Class<T> entityClass, Object id, LockModeType lockModeTyp
 		 */
 		<R> SelectionQuery<R> createQuery(CriteriaQuery<R> criteriaQuery);
 
+		/**
+		 * Create an instance of {@link MutationQuery} for the given criteria update.
+		 *
+		 * @param criteriaUpdate The {@link CriteriaUpdate}
+		 *
+		 * @return The {@link MutationQuery} instance for manipulation and execution
+		 */
+		<R> MutationQuery createQuery(CriteriaUpdate<R> criteriaUpdate);
+
+		/**
+		 * Create an instance of {@link MutationQuery} for the given criteria delete.
+		 *
+		 * @param criteriaDelete The {@link CriteriaDelete}
+		 *
+		 * @return The {@link MutationQuery} instance for manipulation and execution
+		 */
+		<R> MutationQuery createQuery(CriteriaDelete<R> criteriaDelete);
+
 		/**
 		 * Insert a row.
 		 *
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinyStatelessSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinyStatelessSessionImpl.java
index 10567283..47319671 100644
--- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinyStatelessSessionImpl.java
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinyStatelessSessionImpl.java
@@ -7,7 +7,9 @@
 
 import io.smallrye.mutiny.Uni;
 import jakarta.persistence.EntityGraph;
+import jakarta.persistence.criteria.CriteriaDelete;
 import jakarta.persistence.criteria.CriteriaQuery;
+import jakarta.persistence.criteria.CriteriaUpdate;
 import org.hibernate.LockMode;
 import org.hibernate.graph.spi.RootGraphImplementor;
 import org.hibernate.reactive.common.ResultSetMapping;
@@ -53,6 +55,11 @@ public <T> Uni<T> get(Class<T> entityClass, Object id) {
 		return uni( () -> delegate.reactiveGet( entityClass, id ) );
 	}
 
+	@Override
+	public <T> Uni<List<T>> get(Class<T> entityClass, Object... ids) {
+		return uni( () -> delegate.reactiveGet( entityClass, ids ) );
+	}
+
 	@Override
 	public <T> Uni<T> get(Class<T> entityClass, Object id, LockMode lockMode) {
 		return uni( () -> delegate.reactiveGet( entityClass, id, lockMode, null ) );
@@ -114,6 +121,16 @@ public <R> SelectionQuery<R> createQuery(CriteriaQuery<R> criteriaQuery) {
 		return new MutinySelectionQueryImpl<>( delegate.createReactiveQuery( criteriaQuery ), factory );
 	}
 
+	@Override
+	public <R> Mutiny.MutationQuery createQuery(CriteriaUpdate<R> criteriaUpdate) {
+		return new MutinyMutationQueryImpl<>( delegate.createReactiveMutationQuery( criteriaUpdate ), factory );
+	}
+
+	@Override
+	public <R> Mutiny.MutationQuery createQuery(CriteriaDelete<R> criteriaDelete) {
+		return new MutinyMutationQueryImpl<>( delegate.createReactiveMutationQuery( criteriaDelete ), factory );
+	}
+
 	@Override
 	public Uni<Void> insert(Object entity) {
 		return uni( () -> delegate.reactiveInsert( entity ) );
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveEntityPersister.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveEntityPersister.java
index ee35d9fc..0a36d048 100644
--- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveEntityPersister.java
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/persister/entity/impl/ReactiveEntityPersister.java
@@ -16,7 +16,6 @@
 import org.hibernate.generator.values.GeneratedValues;
 import org.hibernate.loader.ast.spi.MultiIdLoadOptions;
 import org.hibernate.persister.entity.EntityPersister;
-import org.hibernate.reactive.persister.entity.mutation.ReactiveInsertCoordinator;
 
 import jakarta.persistence.metamodel.Attribute;
 
@@ -33,16 +32,7 @@ public interface ReactiveEntityPersister extends EntityPersister {
 	 *
 	 * @see EntityPersister#insert(Object, Object[], Object, SharedSessionContractImplementor)
 	 */
-	default CompletionStage<GeneratedValues> insertReactive(Object id, Object[] fields, Object object, SharedSessionContractImplementor session) {
-		return ( (ReactiveInsertCoordinator) getInsertCoordinator() ).reactiveInsert( object, id, fields, session );
-	};
-
-	/**
-	 * Insert the given instance state without blocking, but it allows to specify if it's an identity insert or a regular one.
-	 *
-	 * @see EntityPersister#insert(Object, Object[], Object, SharedSessionContractImplementor)
-	 */
-	CompletionStage<GeneratedValues> insertReactive(Object id, Object[] fields, Object object, SharedSessionContractImplementor session, boolean isIdentityType);
+	CompletionStage<GeneratedValues> insertReactive(Object id, Object[] fields, Object object, SharedSessionContractImplementor session);
 
 	/**
 	 * Insert the given instance state without blocking.
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 c4e688ce..b87ba691 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
@@ -183,12 +183,7 @@ public NaturalIdMapping generateNaturalIdMapping(MappingModelCreationProcess cre
 
 	@Override
 	public CompletionStage<GeneratedValues> insertReactive(Object id, Object[] fields, Object object, SharedSessionContractImplementor session) {
-		return insertReactive( id, fields, object, session, true );
-	}
-
-	@Override
-	public CompletionStage<GeneratedValues> insertReactive(Object id, Object[] fields, Object object, SharedSessionContractImplementor session, boolean isIdentityInsert) {
-		return ( (ReactiveInsertCoordinatorStandard) getInsertCoordinator() ).coordinateReactiveInsert( object, id, fields, session, isIdentityInsert );
+		return ( (ReactiveInsertCoordinatorStandard) getInsertCoordinator() ).coordinateReactiveInsert( object, id, fields, session, false );
 	}
 
 	@Override
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 72b8c040..381a83b7 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
@@ -343,12 +343,7 @@ public CompletionStage<GeneratedValues> insertReactive(Object[] fields, Object e
 
 	@Override
 	public CompletionStage<GeneratedValues> insertReactive(Object id, Object[] fields, Object entity, SharedSessionContractImplementor session) {
-		return insertReactive( id, fields, entity, session, true );
-	}
-
-	@Override
-	public CompletionStage<GeneratedValues> insertReactive(Object id, Object[] fields, Object entity, SharedSessionContractImplementor session, boolean isIdentityInsert) {
-		return ( (ReactiveInsertCoordinatorStandard) getInsertCoordinator() ).coordinateReactiveInsert( entity, id, fields, session, isIdentityInsert );
+		return ( (ReactiveInsertCoordinatorStandard) getInsertCoordinator() ).coordinateReactiveInsert( entity, id, fields, session, false );
 	}
 
 	@Override
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 c844f764..cb7995ac 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
@@ -310,13 +310,7 @@ private CompletionStage<Object> doReactiveLoad(Object id, Object optionalObject,
 
 	@Override
 	public CompletionStage<GeneratedValues> insertReactive(Object id, Object[] fields, Object entity, SharedSessionContractImplementor session) {
-		return insertReactive( id, fields, entity, session, true );
-	}
-
-	@Override
-	public CompletionStage<GeneratedValues> insertReactive(Object id, Object[] fields, Object entity, SharedSessionContractImplementor session, boolean isIdentityInsert) {
-		return ( (ReactiveInsertCoordinatorStandard) getInsertCoordinator() )
-				.coordinateReactiveInsert( entity, id, fields, session, isIdentityInsert );
+		return ( (ReactiveInsertCoordinatorStandard) getInsertCoordinator() ).coordinateReactiveInsert( entity, id, fields, session, false );
 	}
 
 	@Override
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/ReactiveStatelessSession.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/ReactiveStatelessSession.java
index a0b1f81f..33e34917 100644
--- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/ReactiveStatelessSession.java
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/ReactiveStatelessSession.java
@@ -10,6 +10,8 @@
 import org.hibernate.reactive.engine.spi.ReactiveSharedSessionContractImplementor;
 
 import jakarta.persistence.EntityGraph;
+
+import java.util.List;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionStage;
 
@@ -26,11 +28,13 @@
 @Incubating
 public interface ReactiveStatelessSession extends ReactiveQueryProducer, ReactiveSharedSessionContractImplementor {
 
-	<T> CompletionStage<T> reactiveGet(Class<? extends T> entityClass, Object id);
+	<T> CompletionStage<T> reactiveGet(Class<T> entityClass, Object id);
+
+	<T> CompletionStage<List<T>> reactiveGet(Class<T> entityClass, Object... id);
 
 	<T> CompletionStage<T> reactiveGet(String entityName, Object id);
 
-	<T> CompletionStage<T> reactiveGet(Class<? extends T> entityClass, Object id, LockMode lockMode, EntityGraph<T> fetchGraph);
+	<T> CompletionStage<T> reactiveGet(Class<T> entityClass, Object id, LockMode lockMode, EntityGraph<T> fetchGraph);
 
 	<T> CompletionStage<T> reactiveGet(String entityName, Object id, LockMode lockMode, EntityGraph<T> fetchGraph);
 
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 fb20deaa..9f5ca8c9 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
@@ -273,8 +273,8 @@ public <T> CompletionStage<T> reactiveFetch(T association, boolean unproxy) {
 			return nullFuture();
 		}
 
-		if ( association instanceof HibernateProxy ) {
-			LazyInitializer initializer = ( (HibernateProxy) association ).getHibernateLazyInitializer();
+		if ( association instanceof HibernateProxy proxy ) {
+			LazyInitializer initializer = proxy.getHibernateLazyInitializer();
 			if ( !initializer.isUninitialized() ) {
 				return completedFuture( unproxy ? (T) initializer.getImplementation() : association );
 			}
@@ -290,13 +290,12 @@ public <T> CompletionStage<T> reactiveFetch(T association, boolean unproxy) {
 						} );
 			}
 		}
-		else if ( association instanceof PersistentCollection ) {
-			final PersistentCollection<?> persistentCollection = (PersistentCollection<?>) association;
-			if ( persistentCollection.wasInitialized() ) {
+		else if (association instanceof PersistentCollection<?> collection) {
+            if ( collection.wasInitialized() ) {
 				return completedFuture( association );
 			}
 			else {
-				return reactiveInitializeCollection( persistentCollection, false )
+				return reactiveInitializeCollection( collection, false )
 						// don't reassociate the collection instance, because
 						// its owner isn't associated with this session
 						.thenApply( v -> association );
@@ -305,9 +304,8 @@ else if ( association instanceof PersistentCollection ) {
 		else if ( isPersistentAttributeInterceptable( association ) ) {
 			final PersistentAttributeInterceptable interceptable = asPersistentAttributeInterceptable( association );
 			final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor();
-			if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) {
-				EnhancementAsProxyLazinessInterceptor eapli = (EnhancementAsProxyLazinessInterceptor) interceptor;
-				return forceInitialize( association, null, eapli.getIdentifier(), eapli.getEntityName(), this )
+			if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor lazinessInterceptor ) {
+                return forceInitialize( association, null, lazinessInterceptor.getIdentifier(), lazinessInterceptor.getEntityName(), this )
 						.thenApply( i -> association );
 
 			}
@@ -369,7 +367,7 @@ public <R> ReactiveQuery<R> createReactiveQuery(String queryString, Class<R> exp
 		delayedAfterCompletion();
 
 		try {
-			final HqlInterpretation interpretation = interpretHql( queryString, expectedResultType );
+			final HqlInterpretation<?> interpretation = interpretHql( queryString, expectedResultType );
 			final ReactiveQuerySqmImpl<R> query =
 					new ReactiveQuerySqmImpl<>( queryString, interpretation, expectedResultType, this );
 			applyQuerySettingsAndHints( query );
@@ -482,7 +480,7 @@ private <R> ReactiveSelectionQuery<R> interpretAndCreateSelectionQuery(String hq
 		delayedAfterCompletion();
 
 		try {
-			final HqlInterpretation interpretation = interpretHql( hql, resultType );
+			final HqlInterpretation<?> interpretation = interpretHql( hql, resultType );
 			checkSelectionQuery( hql, interpretation );
 			return createSelectionQuery( hql, resultType, interpretation );
 		}
@@ -492,7 +490,7 @@ private <R> ReactiveSelectionQuery<R> interpretAndCreateSelectionQuery(String hq
 		}
 	}
 
-	private <R> ReactiveSelectionQuery<R> createSelectionQuery(String hql, Class<R> resultType, HqlInterpretation interpretation) {
+	private <R> ReactiveSelectionQuery<R> createSelectionQuery(String hql, Class<R> resultType, HqlInterpretation<?> interpretation) {
 		final ReactiveSqmSelectionQueryImpl<R> query =
 				new ReactiveSqmSelectionQueryImpl<>( hql, interpretation, resultType, this );
 		if ( resultType != null ) {
@@ -1671,9 +1669,8 @@ public void setBatchSize(Integer batchSize) {
 	@Override
 	@SuppressWarnings("unchecked")
 	public <T> Class<T> getEntityClass(T entity) {
-		if ( entity instanceof HibernateProxy ) {
-			return (Class<T>) ( (HibernateProxy) entity )
-					.getHibernateLazyInitializer()
+		if ( entity instanceof HibernateProxy proxy ) {
+			return (Class<T>) proxy.getHibernateLazyInitializer()
 					.getPersistentClass();
 		}
 		else {
@@ -1684,8 +1681,8 @@ public <T> Class<T> getEntityClass(T entity) {
 
 	@Override
 	public Object getEntityId(Object entity) {
-		if ( entity instanceof HibernateProxy ) {
-			return ( (HibernateProxy) entity ).getHibernateLazyInitializer()
+		if ( entity instanceof HibernateProxy proxy ) {
+			return proxy.getHibernateLazyInitializer()
 					.getIdentifier();
 		}
 		else {
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java
index 230c4a5f..bc588dae 100644
--- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/impl/ReactiveStatelessSessionImpl.java
@@ -5,6 +5,8 @@
  */
 package org.hibernate.reactive.session.impl;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionStage;
 import java.util.function.Supplier;
@@ -46,6 +48,8 @@
 import org.hibernate.proxy.LazyInitializer;
 import org.hibernate.query.IllegalMutationQueryException;
 import org.hibernate.query.criteria.JpaCriteriaInsert;
+import org.hibernate.query.criteria.JpaCriteriaQuery;
+import org.hibernate.query.criteria.JpaRoot;
 import org.hibernate.query.hql.spi.SqmQueryImplementor;
 import org.hibernate.query.named.NamedResultSetMappingMemento;
 import org.hibernate.query.spi.HqlInterpretation;
@@ -204,18 +208,47 @@ public void checkTransactionNeededForUpdateOperation(String exceptionMessage) {
 	}
 
 	@Override
-	public <T> CompletionStage<T> reactiveGet(Class<? extends T> entityClass, Object id) {
+	public <T> CompletionStage<T> reactiveGet(Class<T> entityClass, Object id) {
 		return reactiveGet( entityClass.getName(), id, LockMode.NONE, null );
 	}
 
+	@Override
+	public <T> CompletionStage<List<T>> reactiveGet(Class<T> entityClass, Object... ids) {
+		checkOpen();
+		for (Object id : ids) {
+			if ( id == null ) {
+				throw new IllegalArgumentException("Null id");
+			}
+		}
+
+		final EntityPersister persister = getEntityPersister( entityClass.getName() );
+
+		final JpaCriteriaQuery<T> query = getCriteriaBuilder().createQuery(entityClass);
+		final JpaRoot<T> from = query.from(entityClass);
+		query.where( from.get( persister.getIdentifierPropertyName() ).in(ids) );
+		return createReactiveQuery(query).getReactiveResultList()
+				.thenApply( resultList -> {
+					final List<Object> idList = new ArrayList<>( resultList.size() );
+					for (T entity : resultList) {
+						idList.add( persister.getIdentifier(entity, this) );
+					}
+					final List<T> list = new ArrayList<>( ids.length );
+					for (Object id : ids) {
+						final int pos = idList.indexOf(id);
+						list.add( pos < 0 ? null : resultList.get(pos) );
+					}
+					return list;
+				});
+	}
+
 	@Override
 	public <T> CompletionStage<T> reactiveGet(String entityName, Object id) {
 		return reactiveGet( entityName, id, LockMode.NONE, null );
 	}
 
 	@Override
-	public <T> CompletionStage<T> reactiveGet(Class<? extends T> entityClass, Object id, LockMode lockMode, EntityGraph<T> fetchGraph) {
-		return reactiveGet( entityClass.getName(), id, LockMode.NONE, fetchGraph );
+	public <T> CompletionStage<T> reactiveGet(Class<T> entityClass, Object id, LockMode lockMode, EntityGraph<T> fetchGraph) {
+		return reactiveGet( entityClass.getName(), id, lockMode, fetchGraph );
 	}
 
 	@Override
@@ -364,8 +397,8 @@ public CompletionStage<Void> reactiveDelete(Object entity) {
 	@Override
 	public CompletionStage<Void> reactiveUpdate(Object entity) {
 		checkOpen();
-		if ( entity instanceof HibernateProxy ) {
-			final LazyInitializer hibernateLazyInitializer = ( (HibernateProxy) entity ).getHibernateLazyInitializer();
+		if ( entity instanceof HibernateProxy proxy ) {
+			final LazyInitializer hibernateLazyInitializer = proxy.getHibernateLazyInitializer();
 			return hibernateLazyInitializer.isUninitialized()
 					? failedFuture( LOG.uninitializedProxyUpdate( entity.getClass() ) )
 					: executeReactiveUpdate( hibernateLazyInitializer.getImplementation() );
@@ -410,7 +443,7 @@ public CompletionStage<Void> reactiveRefresh(String entityName, Object entity) {
 
 	@Override
 	public CompletionStage<Void> reactiveRefresh(Object entity, LockMode lockMode) {
-		return reactiveRefresh( bestGuessEntityName( entity ), entity, LockMode.NONE );
+		return reactiveRefresh( bestGuessEntityName( entity ), entity, lockMode );
 	}
 
 	@Override
@@ -697,22 +730,21 @@ public <T> CompletionStage<T> reactiveFetch(T association, boolean unproxy) {
 				return completedFuture( unproxy ? (T) initializer.getImplementation() : association );
 			}
 		}
-		else if ( association instanceof PersistentCollection ) {
-			final PersistentCollection<?> persistentCollection = (PersistentCollection<?>) association;
-			if ( persistentCollection.wasInitialized() ) {
+		else if ( association instanceof PersistentCollection<?> collection ) {
+            if ( collection.wasInitialized() ) {
 				return completedFuture( association );
 			}
 			else {
 				final ReactiveCollectionPersister collectionDescriptor =
 						(ReactiveCollectionPersister) getFactory().getMappingMetamodel()
-								.getCollectionDescriptor( persistentCollection.getRole() );
+								.getCollectionDescriptor( collection.getRole() );
 
-				final Object key = persistentCollection.getKey();
-				persistenceContext.addUninitializedCollection( collectionDescriptor, persistentCollection, key );
-				persistentCollection.setCurrentSession( this );
+				final Object key = collection.getKey();
+				persistenceContext.addUninitializedCollection( collectionDescriptor, collection, key );
+				collection.setCurrentSession( this );
 				return collectionDescriptor.reactiveInitialize( key, this )
 						.whenComplete( (v, e) -> {
-							persistentCollection.unsetSession( this );
+							collection.unsetSession( this );
 							if ( persistenceContext.isLoadFinished() ) {
 								persistenceContext.clear();
 							}
@@ -723,13 +755,11 @@ else if ( association instanceof PersistentCollection ) {
 		else if ( isPersistentAttributeInterceptable( association ) ) {
 			final PersistentAttributeInterceptable interceptable = asPersistentAttributeInterceptable( association );
 			final PersistentAttributeInterceptor interceptor = interceptable.$$_hibernate_getInterceptor();
-			if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) {
-				final EnhancementAsProxyLazinessInterceptor proxyInterceptor =
-						(EnhancementAsProxyLazinessInterceptor) interceptor;
-				proxyInterceptor.setSession( this );
-				return forceInitialize( association, null, proxyInterceptor.getIdentifier(), proxyInterceptor.getEntityName(), this )
+			if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor lazinessInterceptor ) {
+                lazinessInterceptor.setSession( this );
+				return forceInitialize( association, null, lazinessInterceptor.getIdentifier(), lazinessInterceptor.getEntityName(), this )
 						.whenComplete( (i,e) -> {
-							proxyInterceptor.unsetSession();
+							lazinessInterceptor.unsetSession();
 							if ( persistenceContext.isLoadFinished() ) {
 								persistenceContext.clear();
 							}
@@ -827,7 +857,7 @@ public <R> ReactiveSqmQueryImplementor<R> createReactiveQuery(String queryString
 		delayedAfterCompletion();
 
 		try {
-			final HqlInterpretation interpretation = interpretHql( queryString, expectedResultType );
+			final HqlInterpretation<?> interpretation = interpretHql( queryString, expectedResultType );
 			final ReactiveQuerySqmImpl<R> query =
 					new ReactiveQuerySqmImpl<>( queryString, interpretation, expectedResultType, this );
 			applyQuerySettingsAndHints( query );
@@ -940,7 +970,7 @@ private <R> ReactiveSelectionQuery<R> interpretAndCreateSelectionQuery(String hq
 		delayedAfterCompletion();
 
 		try {
-			final HqlInterpretation interpretation = interpretHql( hql, resultType );
+			final HqlInterpretation<?> interpretation = interpretHql( hql, resultType );
 			checkSelectionQuery( hql, interpretation );
 			return createSelectionQuery( hql, resultType, interpretation );
 		}
@@ -950,7 +980,7 @@ private <R> ReactiveSelectionQuery<R> interpretAndCreateSelectionQuery(String hq
 		}
 	}
 
-	private <R> ReactiveSelectionQuery<R> createSelectionQuery(String hql, Class<R> resultType, HqlInterpretation interpretation) {
+	private <R> ReactiveSelectionQuery<R> createSelectionQuery(String hql, Class<R> resultType, HqlInterpretation<?> interpretation) {
 		final ReactiveSqmSelectionQueryImpl<R> query =
 				new ReactiveSqmSelectionQueryImpl<>( hql, interpretation, resultType, this );
 		if ( resultType != null ) {
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/Stage.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/Stage.java
index 7741201a..1cf8f4fe 100644
--- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/Stage.java
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/Stage.java
@@ -1604,6 +1604,18 @@ interface StatelessSession extends Closeable {
 		 */
 		<T> CompletionStage<T> get(Class<T> entityClass, Object id);
 
+		/**
+		 * Retrieve multiple rows.
+		 *
+		 * @param entityClass The class of the entity to retrieve
+		 * @param ids The ids of the entities to retrieve
+		 *
+		 * @return a list of detached entity instances, via a {@code Uni}
+		 *
+		 * @see org.hibernate.StatelessSession#getMultiple(Class, List)
+		 */
+		<T> CompletionStage<List<T>> get(Class<T> entityClass, Object... ids);
+
 		/**
 		 * Retrieve a row, obtaining the specified lock mode.
 		 *
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageStatelessSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageStatelessSessionImpl.java
index 8a27d5a0..7724a5cd 100644
--- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageStatelessSessionImpl.java
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageStatelessSessionImpl.java
@@ -50,6 +50,11 @@ public <T> CompletionStage<T> get(Class<T> entityClass, Object id) {
 		return delegate.reactiveGet( entityClass, id );
 	}
 
+	@Override
+	public <T> CompletionStage<List<T>> get(Class<T> entityClass, Object... ids) {
+		return delegate.reactiveGet( entityClass, ids );
+	}
+
 	@Override
 	public <T> CompletionStage<T> get(Class<T> entityClass, Object id, LockMode lockMode) {
 		return delegate.reactiveGet( entityClass, id, lockMode, null );


Test patch:
diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MutinyStatelessSessionTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MutinyStatelessSessionTest.java
new file mode 100644
index 00000000..cde496e5
--- /dev/null
+++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/MutinyStatelessSessionTest.java
@@ -0,0 +1,271 @@
+/* Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright: Red Hat Inc. and Hibernate Authors
+ */
+package org.hibernate.reactive;
+
+import io.vertx.junit5.Timeout;
+import io.vertx.junit5.VertxTestContext;
+import jakarta.persistence.*;
+import jakarta.persistence.criteria.*;
+import org.junit.jupiter.api.Test;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static org.junit.jupiter.api.Assertions.*;
+
+@Timeout(value = 10, timeUnit = MINUTES)
+
+public class MutinyStatelessSessionTest extends BaseReactiveTest {
+
+	@Override
+	protected Collection<Class<?>> annotatedEntities() {
+		return List.of( GuineaPig.class );
+	}
+
+	@Test
+	public void testStatelessSession(VertxTestContext context) {
+		GuineaPig pig = new GuineaPig( "Aloi" );
+		test( context, getMutinySessionFactory().withStatelessSession( ss -> ss
+				.insert( pig )
+				.chain( v -> ss.createSelectionQuery( "from GuineaPig where name=:n", GuineaPig.class )
+						.setParameter( "n", pig.name )
+						.getResultList() )
+				.invoke( list -> {
+					assertFalse( list.isEmpty() );
+					assertEquals( 1, list.size() );
+					assertThatPigsAreEqual( pig, list.get( 0 ) );
+				} )
+				.chain( v -> ss.get( GuineaPig.class, pig.id ) )
+				.chain( p -> {
+					assertThatPigsAreEqual( pig, p );
+					p.name = "X";
+					return ss.update( p );
+				} )
+				.chain( v -> ss.refresh( pig ) )
+				.invoke( v -> assertEquals( pig.name, "X" ) )
+				.chain( v -> ss.createMutationQuery( "update GuineaPig set name='Y'" ).executeUpdate() )
+				.chain( v -> ss.refresh( pig ) )
+				.invoke( v -> assertEquals( pig.name, "Y" ) )
+				.chain( v -> ss.delete( pig ) )
+				.chain( v -> ss.createSelectionQuery( "from GuineaPig", GuineaPig.class ).getResultList() )
+				.invoke( list -> assertTrue( list.isEmpty() ) ) )
+		);
+	}
+
+	@Test
+	public void testStatelessSessionWithNamed(VertxTestContext context) {
+		GuineaPig pig = new GuineaPig( "Aloi" );
+		test( context, getMutinySessionFactory().withStatelessSession( ss -> ss
+				.insert( pig )
+				.chain( v -> ss.createNamedQuery( "findbyname", GuineaPig.class )
+						.setParameter( "n", pig.name )
+						.getResultList() )
+				.invoke( list -> {
+					assertFalse( list.isEmpty() );
+					assertEquals( 1, list.size() );
+					assertThatPigsAreEqual( pig, list.get( 0 ) );
+				} )
+				.chain( v -> ss.get( GuineaPig.class, pig.id ) )
+				.chain( p -> {
+					assertThatPigsAreEqual( pig, p );
+					p.name = "X";
+					return ss.update( p );
+				} )
+				.chain( v -> ss.refresh( pig ) )
+				.invoke( v -> assertEquals( pig.name, "X" ) )
+				.chain( v -> ss.createNamedQuery( "updatebyname" ).executeUpdate() )
+				.chain( v -> ss.refresh( pig ) )
+				.invoke( v -> assertEquals( pig.name, "Y" ) )
+				.chain( v -> ss.delete( pig ) )
+				.chain( v -> ss.createNamedQuery( "findall" ).getResultList() )
+				.invoke( list -> assertTrue( list.isEmpty() ) ) )
+		);
+	}
+
+	@Test
+	public void testStatelessSessionWithNative(VertxTestContext context) {
+		GuineaPig pig = new GuineaPig( "Aloi" );
+		test( context, getMutinySessionFactory().openStatelessSession()
+				.chain( ss -> ss.insert( pig )
+						.chain( v -> ss
+								.createNativeQuery( "select * from Piggy where name=:n", GuineaPig.class )
+								.setParameter( "n", pig.name )
+								.getResultList() )
+						.invoke( list -> {
+							assertFalse( list.isEmpty() );
+							assertEquals( 1, list.size() );
+							assertThatPigsAreEqual( pig, list.get( 0 ) );
+						} )
+						.chain( v -> ss.get( GuineaPig.class, pig.id ) )
+						.chain( p -> {
+							assertThatPigsAreEqual( pig, p );
+							p.name = "X";
+							return ss.update( p );
+						} )
+						.chain( v -> ss.refresh( pig ) )
+						.invoke( v -> assertEquals( pig.name, "X" ) )
+						.chain( v -> ss.createNativeQuery( "update Piggy set name='Y'" )
+								.executeUpdate() )
+						.invoke( rows -> assertEquals( 1, rows ) )
+						.chain( v -> ss.refresh( pig ) )
+						.invoke( v -> assertEquals( pig.name, "Y" ) )
+						.chain( v -> ss.delete( pig ) )
+						.chain( v -> ss.createNativeQuery( "select id from Piggy" ).getResultList() )
+						.invoke( list -> assertTrue( list.isEmpty() ) )
+						.chain( v -> ss.close() ) )
+		);
+	}
+
+	@Test
+	public void testStatelessSessionGetMultiple(VertxTestContext context) {
+		GuineaPig a = new GuineaPig("A");
+		GuineaPig b = new GuineaPig("B");
+		GuineaPig c = new GuineaPig("C");
+		test( context, getMutinySessionFactory().openStatelessSession()
+				.chain( ss -> ss.insertMultiple( List.of(a, b, c) )
+						.chain( v -> ss.get( GuineaPig.class, a.id, c.id ) )
+						.invoke( list -> {
+							assertEquals( 2, list.size() );
+							assertThatPigsAreEqual( a, list.get( 0 ) );
+							assertThatPigsAreEqual( c, list.get( 1 ) );
+						})
+						.chain( v -> ss.close() ) )
+		);
+	}
+
+	@Test
+	public void testStatelessSessionCriteria(VertxTestContext context) {
+		GuineaPig pig = new GuineaPig( "Aloi" );
+
+		CriteriaBuilder cb = getSessionFactory().getCriteriaBuilder();
+
+		CriteriaQuery<GuineaPig> query = cb.createQuery( GuineaPig.class );
+		Root<GuineaPig> gp = query.from( GuineaPig.class );
+		query.where( cb.equal( gp.get( "name" ), cb.parameter( String.class, "n" ) ) );
+
+		CriteriaUpdate<GuineaPig> update = cb.createCriteriaUpdate( GuineaPig.class );
+		update.from( GuineaPig.class );
+		update.set( "name", "Bob" );
+
+		CriteriaDelete<GuineaPig> delete = cb.createCriteriaDelete( GuineaPig.class );
+		delete.from( GuineaPig.class );
+
+		test( context, getMutinySessionFactory().openStatelessSession()
+				.chain( ss -> ss.insert( pig )
+						.chain( v -> ss.createQuery( query )
+								.setParameter( "n", pig.name )
+								.getResultList() )
+						.invoke( list -> {
+							assertFalse( list.isEmpty() );
+							assertEquals( 1, list.size() );
+							assertThatPigsAreEqual( pig, list.get( 0 ) );
+						} )
+						.chain( v -> ss.createQuery( update ).executeUpdate() )
+						.invoke( rows -> assertEquals( 1, rows ) )
+						.chain( v -> ss.createQuery( delete ).executeUpdate() )
+						.invoke( rows -> assertEquals( 1, rows ) )
+						.chain( v -> ss.close() ) )
+		);
+	}
+
+	@Test
+	public void testTransactionPropagation(VertxTestContext context) {
+		test( context, getMutinySessionFactory().withStatelessSession(
+				session -> session.withTransaction( transaction -> session.createSelectionQuery( "from GuineaPig", GuineaPig.class )
+						.getResultList()
+						.chain( list -> {
+							assertNotNull( session.currentTransaction() );
+							assertFalse( session.currentTransaction().isMarkedForRollback() );
+							session.currentTransaction().markForRollback();
+							assertTrue( session.currentTransaction().isMarkedForRollback() );
+							assertTrue( transaction.isMarkedForRollback() );
+							return session.withTransaction( t -> {
+								assertTrue( t.isMarkedForRollback() );
+								return session.createSelectionQuery( "from GuineaPig", GuineaPig.class ).getResultList();
+							} );
+						} ) )
+		) );
+	}
+
+	@Test
+	public void testSessionPropagation(VertxTestContext context) {
+		test( context, getMutinySessionFactory().withStatelessSession(
+				session -> session.createSelectionQuery( "from GuineaPig", GuineaPig.class ).getResultList()
+						.chain( list -> getMutinySessionFactory().withStatelessSession( s -> {
+							assertEquals( session, s );
+							return s.createSelectionQuery( "from GuineaPig", GuineaPig.class ).getResultList();
+						} ) )
+		) );
+	}
+
+	private void assertThatPigsAreEqual( GuineaPig expected, GuineaPig actual) {
+		assertNotNull( actual );
+		assertEquals( expected.getId(), actual.getId() );
+		assertEquals( expected.getName(), actual.getName() );
+	}
+
+	@NamedQuery(name = "findbyname", query = "from GuineaPig where name=:n")
+	@NamedQuery(name = "updatebyname", query = "update GuineaPig set name='Y'")
+	@NamedQuery(name = "findall", query = "from GuineaPig")
+
+	@Entity(name = "GuineaPig")
+	@Table(name = "Piggy")
+	public static class GuineaPig {
+		@Id
+		@GeneratedValue
+		private Integer id;
+		private String name;
+		@Version
+		private int version;
+
+		public GuineaPig() {
+		}
+
+		public GuineaPig(String name) {
+			this.name = name;
+		}
+
+		public Integer getId() {
+			return id;
+		}
+
+		public void setId(Integer id) {
+			this.id = id;
+		}
+
+		public String getName() {
+			return name;
+		}
+
+		public void setName(String name) {
+			this.name = name;
+		}
+
+		@Override
+		public String toString() {
+			return id + ": " + name;
+		}
+
+		@Override
+		public boolean equals(Object o) {
+			if ( this == o ) {
+				return true;
+			}
+			if ( o == null || getClass() != o.getClass() ) {
+				return false;
+			}
+			GuineaPig guineaPig = (GuineaPig) o;
+			return Objects.equals( name, guineaPig.name );
+		}
+
+		@Override
+		public int hashCode() {
+			return Objects.hash( name );
+		}
+	}
+}
diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveStatelessSessionTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveStatelessSessionTest.java
index c2f6ac31..b4d2cbf1 100644
--- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveStatelessSessionTest.java
+++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ReactiveStatelessSessionTest.java
@@ -135,6 +135,23 @@ public void testStatelessSessionWithNative(VertxTestContext context) {
 		);
 	}
 
+	@Test
+	public void testStatelessSessionGetMultiple(VertxTestContext context) {
+		GuineaPig a = new GuineaPig("A");
+		GuineaPig b = new GuineaPig("B");
+		GuineaPig c = new GuineaPig("C");
+		test( context, getSessionFactory().openStatelessSession()
+				.thenCompose( ss -> ss.insertMultiple( List.of(a, b, c) )
+						.thenCompose( v -> ss.get( GuineaPig.class, a.id, c.id ) )
+						.thenAccept( list -> {
+							assertEquals( 2, list.size() );
+							assertThatPigsAreEqual( a, list.get( 0 ) );
+							assertThatPigsAreEqual( c, list.get( 1 ) );
+						})
+						.thenCompose( v -> ss.close() ) )
+		);
+	}
+
 	@Test
 	public void testStatelessSessionCriteria(VertxTestContext context) {
 		GuineaPig pig = new GuineaPig( "Aloi" );