From 0064771d2f31fbcce7c5eedfdef7490219fff238 Mon Sep 17 00:00:00 2001
From: Andrea Boriero
Date: Fri, 17 Jan 2025 10:31:55 +0100
Subject: [PATCH 1/4] [#2006] UnexpectedAccessToTheDatabase error when merging
a detached entity with a ToMany association
---
.../reactive/engine/impl/CollectionTypes.java | 507 ++++++++++++++++++
.../reactive/engine/impl/EntityTypes.java | 215 +++++---
.../hibernate/reactive/logging/impl/Log.java | 4 +
3 files changed, 652 insertions(+), 74 deletions(-)
create mode 100644 hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CollectionTypes.java
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CollectionTypes.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CollectionTypes.java
new file mode 100644
index 000000000..99e227cbc
--- /dev/null
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CollectionTypes.java
@@ -0,0 +1,507 @@
+/* Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright: Red Hat Inc. and Hibernate Authors
+ */
+package org.hibernate.reactive.engine.impl;
+
+import java.io.Serializable;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.concurrent.CompletionStage;
+
+import org.hibernate.Hibernate;
+import org.hibernate.HibernateException;
+import org.hibernate.collection.spi.AbstractPersistentCollection;
+import org.hibernate.collection.spi.PersistentArrayHolder;
+import org.hibernate.collection.spi.PersistentCollection;
+import org.hibernate.engine.spi.CollectionEntry;
+import org.hibernate.engine.spi.PersistenceContext;
+import org.hibernate.engine.spi.SessionImplementor;
+import org.hibernate.engine.spi.SharedSessionContractImplementor;
+import org.hibernate.metamodel.mapping.PluralAttributeMapping;
+import org.hibernate.persister.collection.CollectionPersister;
+import org.hibernate.reactive.logging.impl.Log;
+import org.hibernate.reactive.logging.impl.LoggerFactory;
+import org.hibernate.type.ArrayType;
+import org.hibernate.type.CollectionType;
+import org.hibernate.type.CustomCollectionType;
+import org.hibernate.type.EntityType;
+import org.hibernate.type.ForeignKeyDirection;
+import org.hibernate.type.MapType;
+import org.hibernate.type.Type;
+
+import static org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer.UNFETCHED_PROPERTY;
+import static org.hibernate.internal.util.collections.CollectionHelper.mapOfSize;
+import static org.hibernate.pretty.MessageHelper.collectionInfoString;
+import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture;
+import static org.hibernate.reactive.util.impl.CompletionStages.loop;
+import static org.hibernate.reactive.util.impl.CompletionStages.nullFuture;
+import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture;
+
+/**
+ * Reactive operations that really belong to {@link CollectionType}
+ *
+ */
+public class CollectionTypes {
+ private static final Log LOG = LoggerFactory.make( Log.class, MethodHandles.lookup() );
+
+ /**
+ * @see org.hibernate.type.AbstractType#replace(Object, Object, SharedSessionContractImplementor, Object, Map, ForeignKeyDirection)
+ */
+ public static CompletionStage
+ // One thing to be careful of here is a "bare" original collection
+ // in which case we should never ever ever reset the dirty flag
+ // on the target because we simply do not know...
+ if ( original instanceof PersistentCollection> originalPersistentCollection
+ && result instanceof PersistentCollection> resultPersistentCollection ) {
+ return preserveSnapshot(
+ originalPersistentCollection, resultPersistentCollection,
+ elemType, owner, copyCache, session
+ ).thenApply( v -> {
+ if ( !originalPersistentCollection.isDirty() ) {
+ resultPersistentCollection.clearDirty();
+ }
+ return result;
+ } );
+ }
+ else {
+ return completedFuture( result );
+ }
+ } );
+ }
+
+ private static CompletionStage replaceMapTypeElements(
+ CollectionType type,
+ Map original,
+ Map target,
+ Object owner,
+ Map copyCache,
+ SessionImplementor session) {
+ final CollectionPersister persister =
+ session.getFactory().getRuntimeMetamodels().getMappingMetamodel()
+ .getCollectionDescriptor( type.getRole() );
+
+ final Map result = target;
+ result.clear();
+
+ return loop(
+ original.entrySet(), entry -> {
+ final Map.Entry me = entry;
+ return getReplace( persister.getIndexType(), me.getKey(), owner, session, copyCache )
+ .thenCompose( key ->
+ getReplace(
+ persister.getElementType(),
+ me.getValue(),
+ owner,
+ session,
+ copyCache
+ ).thenAccept( value ->
+ result.put( key, value ) )
+ );
+ }
+ ).thenApply( unused -> result );
+ }
+
+ private static CompletionStage replaceArrayTypeElements(
+ CollectionType type,
+ Object original,
+ Object target,
+ Object owner,
+ Map copyCache,
+ SessionImplementor session) {
+ final Object result;
+ final int length = Array.getLength( original );
+ if ( length != Array.getLength( target ) ) {
+ //note: this affects the return value!
+ result = ( (ArrayType) type ).instantiateResult( original );
+ }
+ else {
+ result = target;
+ }
+
+ final Type elemType = type.getElementType( session.getFactory() );
+ return loop(
+ 0, length, i -> {
+ return getReplace( elemType, Array.get( original, i ), owner, session, copyCache )
+ .thenApply( o -> {
+ Array.set( result, i, o );
+ return result;
+ }
+ );
+ }
+ ).thenApply( unused -> result );
+ }
+
+ private static CompletionStage getReplace(
+ Type elemType,
+ Object o,
+ Object owner,
+ SessionImplementor session,
+ Map copyCache) {
+ return getReplace( elemType, o, null, owner, session, copyCache );
+ }
+
+ private static CompletionStage getReplace(
+ Type elemType,
+ Object o,
+ Object target,
+ Object owner,
+ SessionImplementor session,
+ Map copyCache) {
+ if ( elemType instanceof EntityType ) {
+ return EntityTypes.replace( (EntityType) elemType, o, target, session, owner, copyCache );
+ }
+ else {
+ final Object replace = elemType.replace( o, target, session, owner, copyCache );
+ return completedFuture( replace );
+ }
+ }
+
+ /**
+ * @see CollectionType#preserveSnapshot(PersistentCollection, PersistentCollection, Type, Object, Map, SharedSessionContractImplementor)
+ */
+ private static CompletionStage preserveSnapshot(
+ PersistentCollection> original,
+ PersistentCollection> result,
+ Type elemType,
+ Object owner,
+ Map copyCache,
+ SessionImplementor session) {
+ final CollectionEntry ce = session.getPersistenceContextInternal().getCollectionEntry( result );
+ if ( ce != null ) {
+ return createSnapshot( original, result, elemType, owner, copyCache, session )
+ .thenAccept( serializable ->
+ ce.resetStoredSnapshot( result, serializable ) );
+ }
+ return voidFuture();
+ }
+
+ /**
+ * @see CollectionType#createSnapshot(PersistentCollection, PersistentCollection, Type, Object, Map, SharedSessionContractImplementor)
+ */
+ private static CompletionStage createSnapshot(
+ PersistentCollection> original,
+ PersistentCollection> result,
+ Type elemType,
+ Object owner,
+ Map copyCache,
+ SessionImplementor session) {
+ final Serializable originalSnapshot = original.getStoredSnapshot();
+ if ( originalSnapshot instanceof List> list ) {
+ return createListSnapshot( list, elemType, owner, copyCache, session );
+ }
+ else if ( originalSnapshot instanceof Map, ?> map ) {
+ return createMapSnapshot( map, result, elemType, owner, copyCache, session );
+ }
+ else if ( originalSnapshot instanceof Object[] array ) {
+ return createArraySnapshot( array, elemType, owner, copyCache, session );
+ }
+ else {
+ // retain the same snapshot
+ return completedFuture( result.getStoredSnapshot() );
+ }
+ }
+
+ /**
+ * @see CollectionType#createArraySnapshot(Object[], Type, Object, Map, SharedSessionContractImplementor)
+ */
+ private static CompletionStage createArraySnapshot(
+ Object[] array,
+ Type elemType,
+ Object owner,
+ Map copyCache,
+ SessionImplementor session) {
+ return loop(
+ 0, array.length,
+ i ->
+ getReplace( elemType, array[i], owner, session, copyCache )
+ .thenCompose( o -> {
+ array[i] = o;
+ return voidFuture();
+ }
+ )
+ ).thenApply( unused -> array );
+ }
+
+ /**
+ * @see CollectionType#createMapSnapshot(Map, PersistentCollection, Type, Object, Map, SharedSessionContractImplementor)
+ */
+ private static CompletionStage createMapSnapshot(
+ Map, ?> map,
+ PersistentCollection> result,
+ Type elemType,
+ Object owner,
+ Map copyCache,
+ SessionImplementor session) {
+ final Map, ?> resultSnapshot = (Map, ?>) result.getStoredSnapshot();
+ final Map targetMap;
+ if ( map instanceof SortedMap, ?> sortedMap ) {
+ //noinspection unchecked, rawtypes
+ targetMap = new TreeMap( sortedMap.comparator() );
+ }
+ else {
+ targetMap = mapOfSize( map.size() );
+ }
+ return loop(
+ map.entrySet(), entry ->
+ getReplace( elemType, entry.getValue(), resultSnapshot, owner, session, copyCache )
+ .thenCompose( newValue -> {
+ final Object key = entry.getKey();
+ targetMap.put( key == entry.getValue() ? newValue : key, newValue );
+ return voidFuture();
+ } )
+ ).thenApply( v -> (Serializable) targetMap );
+ }
+
+ /**
+ * @see CollectionType#createListSnapshot(List, Type, Object, Map, SharedSessionContractImplementor)
+ */
+ private static CompletionStage createListSnapshot(
+ List> list,
+ Type elemType,
+ Object owner,
+ Map copyCache,
+ SessionImplementor session) {
+ final ArrayList targetList = new ArrayList<>( list.size() );
+ return loop(
+ list, obj ->
+ getReplace( elemType, obj, owner, session, copyCache )
+ .thenCompose( o -> {
+ targetList.add( o );
+ return voidFuture();
+ } )
+ ).thenApply( unused -> targetList );
+ }
+
+ /**
+ * @see CollectionType#instantiateResultIfNecessary(Object, Object)
+ */
+ private static Object instantiateResultIfNecessary(CollectionType type, Object original, Object target) {
+ // for a null target, or a target which is the same as the original,
+ // we need to put the merged elements in a new collection
+ // by default just use an unanticipated capacity since we don't
+ // know how to extract the capacity to use from original here...
+ return target == null
+ || target == original
+ || target == UNFETCHED_PROPERTY
+ || target instanceof PersistentCollection> collection && collection.isWrapper( original ) ?
+ type.instantiate( -1 ) :
+ target;
+ }
+
+
+}
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/EntityTypes.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/EntityTypes.java
index 879f7ddc8..722ba4a78 100644
--- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/EntityTypes.java
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/EntityTypes.java
@@ -25,6 +25,7 @@
import org.hibernate.reactive.persister.entity.impl.ReactiveEntityPersister;
import org.hibernate.reactive.session.impl.ReactiveQueryExecutorLookup;
import org.hibernate.reactive.session.impl.ReactiveSessionImpl;
+import org.hibernate.type.CollectionType;
import org.hibernate.type.EntityType;
import org.hibernate.type.ForeignKeyDirection;
import org.hibernate.type.OneToOneType;
@@ -158,42 +159,11 @@ public static CompletionStage replace(
final Object owner,
final Map copyCache) {
Object[] copied = new Object[original.length];
- for ( int i = 0; i < types.length; i++ ) {
- if ( original[i] == UNFETCHED_PROPERTY || original[i] == UNKNOWN ) {
- copied[i] = target[i];
- }
- else {
- if ( !( types[i] instanceof EntityType ) ) {
- copied[i] = types[i].replace(
- original[i],
- target[i] == UNFETCHED_PROPERTY ? null : target[i],
- session,
- owner,
- copyCache
- );
- }
- }
- }
- return loop( 0, types.length,
- i -> original[i] != UNFETCHED_PROPERTY && original[i] != UNKNOWN
- && types[i] instanceof EntityType,
- i -> replace(
- (EntityType) types[i],
- original[i],
- target[i] == UNFETCHED_PROPERTY ? null : target[i],
- session,
- owner,
- copyCache
- ).thenCompose( copy -> {
- if ( copy instanceof CompletionStage ) {
- return ( (CompletionStage>) copy )
- .thenAccept( nonStageCopy -> copied[i] = nonStageCopy );
- }
- else {
- copied[i] = copy;
- return voidFuture();
- }
- } )
+ return loop(
+ 0, types.length,
+ i ->
+ replace( original, target, types, session, owner, copyCache, i, copied )
+
).thenApply( v -> copied );
}
@@ -209,43 +179,11 @@ public static CompletionStage replace(
final Map copyCache,
final ForeignKeyDirection foreignKeyDirection) {
Object[] copied = new Object[original.length];
- for ( int i = 0; i < types.length; i++ ) {
- if ( original[i] == UNFETCHED_PROPERTY || original[i] == UNKNOWN ) {
- copied[i] = target[i];
- }
- else {
- if ( !( types[i] instanceof EntityType ) ) {
- copied[i] = types[i].replace(
- original[i],
- target[i] == UNFETCHED_PROPERTY ? null : target[i],
- session,
- owner,
- copyCache,
- foreignKeyDirection
- );
- }
- }
- }
- return loop( 0, types.length,
- i -> original[i] != UNFETCHED_PROPERTY && original[i] != UNKNOWN
- && types[i] instanceof EntityType,
- i -> replace(
- (EntityType) types[i],
- original[i],
- target[i] == UNFETCHED_PROPERTY ? null : target[i],
- session,
- owner,
- copyCache,
- foreignKeyDirection
- ).thenCompose( copy -> {
- if ( copy instanceof CompletionStage ) {
- return ( (CompletionStage>) copy ).thenAccept( nonStageCopy -> copied[i] = nonStageCopy );
- }
- else {
- copied[i] = copy;
- return voidFuture();
- }
- } )
+ return loop(
+ 0, types.length,
+ i ->
+ replace( original, target, types, session, owner, copyCache, foreignKeyDirection, i, copied )
+
).thenApply( v -> copied );
}
@@ -272,7 +210,7 @@ private static CompletionStage replace(
/**
* @see EntityType#replace(Object, Object, SharedSessionContractImplementor, Object, Map)
*/
- private static CompletionStage replace(
+ protected static CompletionStage replace(
EntityType entityType,
Object original,
Object target,
@@ -450,4 +388,133 @@ private static CompletionStage loadHibernateProxyEntity(
}
}
+ private static CompletionStage replace(
+ Object[] original,
+ Object[] target,
+ Type[] types,
+ SessionImplementor session,
+ Object owner,
+ Map copyCache,
+ int i,
+ Object[] copied) {
+ if ( original[i] == UNFETCHED_PROPERTY || original[i] == UNKNOWN ) {
+ copied[i] = target[i];
+ return voidFuture();
+ }
+ else if ( types[i] instanceof CollectionType ) {
+ return CollectionTypes.replace(
+ (CollectionType) types[i],
+ original[i],
+ target[i] == UNFETCHED_PROPERTY ? null : target[i],
+ session,
+ owner,
+ copyCache
+ ).thenCompose( copy -> {
+ if ( copy instanceof CompletionStage ) {
+ return ( (CompletionStage>) copy ).thenAccept( nonStageCopy -> copied[i] = nonStageCopy );
+ }
+ else {
+ copied[i] = copy;
+ return voidFuture();
+ }
+ } );
+ }
+ else if ( types[i] instanceof EntityType ) {
+ return replace(
+ (EntityType) types[i],
+ original[i],
+ target[i] == UNFETCHED_PROPERTY ? null : target[i],
+ session,
+ owner,
+ copyCache
+ ).thenCompose( copy -> {
+ if ( copy instanceof CompletionStage ) {
+ return ( (CompletionStage>) copy )
+ .thenAccept( nonStageCopy -> copied[i] = nonStageCopy );
+ }
+ else {
+ copied[i] = copy;
+ return voidFuture();
+ }
+ } );
+ }
+ else {
+ final Type type = types[i];
+ copied[i] = type.replace(
+ original[i],
+ target[i] == UNFETCHED_PROPERTY ? null : target[i],
+ session,
+ owner,
+ copyCache
+ );
+ return voidFuture();
+ }
+ }
+
+ private static CompletionStage replace(
+ Object[] original,
+ Object[] target,
+ Type[] types,
+ SessionImplementor session,
+ Object owner,
+ Map copyCache,
+ ForeignKeyDirection foreignKeyDirection,
+ int i,
+ Object[] copied) {
+ if ( original[i] == UNFETCHED_PROPERTY || original[i] == UNKNOWN ) {
+ copied[i] = target[i];
+ return voidFuture();
+ }
+ else if ( types[i] instanceof CollectionType ) {
+ return CollectionTypes.replace(
+ (CollectionType) types[i],
+ original[i],
+ target[i] == UNFETCHED_PROPERTY ? null : target[i],
+ session,
+ owner,
+ copyCache,
+ foreignKeyDirection
+ ).thenCompose( copy -> {
+ if ( copy instanceof CompletionStage ) {
+ return ( (CompletionStage>) copy ).thenAccept( nonStageCopy -> copied[i] = nonStageCopy );
+ }
+ else {
+ copied[i] = copy;
+ return voidFuture();
+ }
+ } );
+ }
+ else if ( types[i] instanceof EntityType ) {
+ return replace(
+ (EntityType) types[i],
+ original[i],
+ target[i] == UNFETCHED_PROPERTY ? null : target[i],
+ session,
+ owner,
+ copyCache,
+ foreignKeyDirection
+ ).thenCompose( copy -> {
+ if ( copy instanceof CompletionStage ) {
+ return ( (CompletionStage>) copy ).thenAccept( nonStageCopy -> copied[i] = nonStageCopy );
+ }
+ else {
+ copied[i] = copy;
+ return voidFuture();
+ }
+ } );
+ }
+ else {
+ copied[i] = types[i].replace(
+ original[i],
+ target[i] == UNFETCHED_PROPERTY ? null : target[i],
+ session,
+ owner,
+ copyCache,
+ foreignKeyDirection
+ );
+ return voidFuture();
+ }
+ }
+
+
}
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/logging/impl/Log.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/logging/impl/Log.java
index 9c6c126cc..75287e5be 100644
--- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/logging/impl/Log.java
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/logging/impl/Log.java
@@ -311,4 +311,8 @@ public interface Log extends BasicLogger {
@LogMessage(level = WARN)
@Message(id = 448, value = "Warnings creating temp table : %s")
void warningsCreatingTempTable(SQLWarning warning);
+
+ @LogMessage(level = WARN)
+ @Message( id= 494, value = "Attempt to merge an uninitialized collection with queued operations; queued operations will be ignored: %s")
+ void ignoreQueuedOperationsOnMerge(String collectionInfoString);
}
From 9f84156e39802ce3da8df905afc598a847027def Mon Sep 17 00:00:00 2001
From: Andrea Boriero
Date: Mon, 20 Jan 2025 10:48:36 +0100
Subject: [PATCH 2/4] [#2006] Add test for UnexpectedAccessToTheDatabase error
when merging a detached entity with a ToMany association
---
.../reactive/OneToManyArrayMergeTest.java | 190 +++++++++++++++
.../reactive/OneToManyMapMergeTest.java | 199 +++++++++++++++
.../reactive/OneToManyMergeTest.java | 227 ++++++++++++++++++
3 files changed, 616 insertions(+)
create mode 100644 hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToManyArrayMergeTest.java
create mode 100644 hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToManyMapMergeTest.java
create mode 100644 hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToManyMergeTest.java
diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToManyArrayMergeTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToManyArrayMergeTest.java
new file mode 100644
index 000000000..e2eb826d9
--- /dev/null
+++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToManyArrayMergeTest.java
@@ -0,0 +1,190 @@
+/* Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright: Red Hat Inc. and Hibernate Authors
+ */
+package org.hibernate.reactive;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import io.vertx.junit5.Timeout;
+import io.vertx.junit5.VertxTestContext;
+import jakarta.persistence.Entity;
+import jakarta.persistence.FetchType;
+import jakarta.persistence.Id;
+import jakarta.persistence.OneToMany;
+import jakarta.persistence.Table;
+
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static org.assertj.core.api.Assertions.assertThat;
+
+@Timeout(value = 2, timeUnit = MINUTES)
+public class OneToManyArrayMergeTest extends BaseReactiveTest {
+
+ private final static Long USER_ID = 1L;
+ private final static Long ADMIN_ROLE_ID = 2L;
+ private final static Long USER_ROLE_ID = 3L;
+ private final static String UPDATED_FIRSTNAME = "UPDATED FIRSTNAME";
+ private final static String UPDATED_LASTNAME = "UPDATED LASTNAME";
+
+ @Override
+ protected Collection> annotatedEntities() {
+ return List.of( User.class, Role.class );
+ }
+
+ @BeforeEach
+ public void populateDb(VertxTestContext context) {
+ Role adminRole = new Role( ADMIN_ROLE_ID, "admin" );
+ Role userRole = new Role( USER_ROLE_ID, "user" );
+ User user = new User( USER_ID, "first", "last", adminRole );
+ test(
+ context, getMutinySessionFactory()
+ .withTransaction( s -> s.persistAll( user, adminRole, userRole ) )
+ );
+ }
+
+ @Test
+ public void testMerge(VertxTestContext context) {
+ test(
+ context, getMutinySessionFactory()
+ .withTransaction( s -> s.find( User.class, USER_ID ) )
+ .chain( user -> getMutinySessionFactory()
+ .withTransaction( s -> s
+ .createQuery( "FROM Role", Role.class )
+ .getResultList() )
+ .map( roles -> {
+ user.addAll( roles );
+ user.setFirstname( UPDATED_FIRSTNAME );
+ user.setLastname( UPDATED_LASTNAME );
+ return user;
+ } )
+ )
+ .chain( user -> {
+ assertThat( user.getFirstname() ).isEqualTo( UPDATED_FIRSTNAME );
+ assertThat( user.getLastname() ).isEqualTo( UPDATED_LASTNAME );
+ assertThat( user.getRoles() ).hasSize( 2 );
+ return getMutinySessionFactory()
+ .withTransaction( s -> s.merge( user ) );
+ }
+ )
+ .chain( v -> getMutinySessionFactory()
+ .withTransaction( s -> s.find( User.class, USER_ID ) )
+ )
+ .invoke( user -> {
+ Role adminRole = new Role( ADMIN_ROLE_ID, "admin" );
+ Role userRole = new Role( USER_ROLE_ID, "user" );
+ assertThat( user.getFirstname() ).isEqualTo( UPDATED_FIRSTNAME );
+ assertThat( user.getLastname() ).isEqualTo( UPDATED_LASTNAME );
+ assertThat( user.getRoles() ).containsExactlyInAnyOrder(
+ adminRole,
+ userRole
+ );
+ }
+ )
+ );
+ }
+
+ @Entity(name = "User")
+ @Table(name = "USER_TABLE")
+ public static class User {
+
+ @Id
+ private Long id;
+
+ private String firstname;
+
+ private String lastname;
+
+ @OneToMany(fetch = FetchType.EAGER)
+ private Role[] roles;
+
+ public User() {
+ }
+
+ public User(Long id, String firstname, String lastname, Role... roles) {
+ this.id = id;
+ this.firstname = firstname;
+ this.lastname = lastname;
+ this.roles = new Role[roles.length];
+ for ( int i = 0; i < roles.length; i++ ) {
+ this.roles[i] = roles[i];
+ }
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public String getFirstname() {
+ return firstname;
+ }
+
+ public void setFirstname(String firstname) {
+ this.firstname = firstname;
+ }
+
+ public String getLastname() {
+ return lastname;
+ }
+
+ public void setLastname(String lastname) {
+ this.lastname = lastname;
+ }
+
+ public Role[] getRoles() {
+ return roles;
+ }
+
+ public void addAll(List roles) {
+ this.roles = new Role[roles.size()];
+ for ( int i = 0; i < roles.size(); i++ ) {
+ this.roles[i] = roles.get( i );
+ }
+ }
+ }
+
+ @Entity(name = "Role")
+ @Table(name = "ROLE_TABLE")
+ public static class Role {
+
+ @Id
+ private Long id;
+ private String code;
+
+ public Role() {
+ }
+
+ public Role(Long id, String code) {
+ this.id = id;
+ this.code = code;
+ }
+
+ public Object getId() {
+ return id;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if ( o == null || getClass() != o.getClass() ) {
+ return false;
+ }
+ Role role = (Role) o;
+ return Objects.equals( id, role.id ) && Objects.equals( code, role.code );
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash( id, code );
+ }
+
+ @Override
+ public String toString() {
+ return "Role{" + code + '}';
+ }
+ }
+}
diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToManyMapMergeTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToManyMapMergeTest.java
new file mode 100644
index 000000000..7a1096d3d
--- /dev/null
+++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToManyMapMergeTest.java
@@ -0,0 +1,199 @@
+/* Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright: Red Hat Inc. and Hibernate Authors
+ */
+package org.hibernate.reactive;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import io.vertx.junit5.Timeout;
+import io.vertx.junit5.VertxTestContext;
+import jakarta.persistence.Entity;
+import jakarta.persistence.FetchType;
+import jakarta.persistence.Id;
+import jakarta.persistence.OneToMany;
+import jakarta.persistence.Table;
+
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static org.assertj.core.api.Assertions.assertThat;
+
+@Timeout(value = 2, timeUnit = MINUTES)
+public class OneToManyMapMergeTest extends BaseReactiveTest {
+
+ private final static Long USER_ID = 1L;
+ private final static Long ADMIN_ROLE_ID = 2L;
+ private final static Long USER_ROLE_ID = 3L;
+ private final static String UPDATED_FIRSTNAME = "UPDATED FIRSTNAME";
+ private final static String UPDATED_LASTNAME = "UPDATED LASTNAME";
+
+ @Override
+ protected Collection> annotatedEntities() {
+ return List.of( User.class, Role.class );
+ }
+
+ @BeforeEach
+ public void populateDb(VertxTestContext context) {
+ Role adminRole = new Role( ADMIN_ROLE_ID, "admin" );
+ Role userRole = new Role( USER_ROLE_ID, "user" );
+ User user = new User( USER_ID, "first", "last", adminRole );
+ test(
+ context, getMutinySessionFactory()
+ .withTransaction( s -> s.persistAll( user, adminRole, userRole ) )
+ );
+ }
+
+ @Test
+ public void testMerge(VertxTestContext context) {
+ test(
+ context, getMutinySessionFactory()
+ .withTransaction( s -> s.find( User.class, USER_ID ) )
+ .chain( user -> getMutinySessionFactory()
+ .withTransaction( s -> s
+ .createQuery( "FROM Role", Role.class )
+ .getResultList() )
+ .map( roles -> {
+ user.addAll( roles );
+ user.setFirstname( UPDATED_FIRSTNAME );
+ user.setLastname( UPDATED_LASTNAME );
+ return user;
+ } )
+ )
+ .chain( user -> {
+ assertThat( user.getFirstname() ).isEqualTo( UPDATED_FIRSTNAME );
+ assertThat( user.getLastname() ).isEqualTo( UPDATED_LASTNAME );
+ assertThat( user.getRoles() ).hasSize( 2 );
+ return getMutinySessionFactory()
+ .withTransaction( s -> s.merge( user ) );
+ }
+ )
+ .chain( v -> getMutinySessionFactory()
+ .withTransaction( s -> s.find( User.class, USER_ID ) )
+ )
+ .invoke( user -> {
+ Role adminRole = new Role( ADMIN_ROLE_ID, "admin" );
+ Role userRole = new Role( USER_ROLE_ID, "user" );
+ assertThat( user.getFirstname() ).isEqualTo( UPDATED_FIRSTNAME );
+ assertThat( user.getLastname() ).isEqualTo( UPDATED_LASTNAME );
+ assertThat( user.getRoles() ).containsEntry(
+ adminRole.getCode(),
+ adminRole
+ );
+ assertThat( user.getRoles() ).containsEntry(
+ userRole.getCode(),
+ userRole
+ );
+ }
+ )
+ );
+ }
+
+ @Entity(name = "User")
+ @Table(name = "USER_TABLE")
+ public static class User {
+
+ @Id
+ private Long id;
+
+ private String firstname;
+
+ private String lastname;
+
+ @OneToMany(fetch = FetchType.EAGER)
+ private Map roles = new HashMap();
+
+ public User() {
+ }
+
+ public User(Long id, String firstname, String lastname, Role... roles) {
+ this.id = id;
+ this.firstname = firstname;
+ this.lastname = lastname;
+ for ( Role role : roles ) {
+ this.roles.put( role.getCode(), role );
+ }
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public String getFirstname() {
+ return firstname;
+ }
+
+ public void setFirstname(String firstname) {
+ this.firstname = firstname;
+ }
+
+ public String getLastname() {
+ return lastname;
+ }
+
+ public void setLastname(String lastname) {
+ this.lastname = lastname;
+ }
+
+ public Map getRoles() {
+ return roles;
+ }
+
+ public void addAll(List roles) {
+ this.roles.clear();
+ for ( Role role : roles ) {
+ this.roles.put( role.getCode(), role );
+ }
+ }
+ }
+
+ @Entity(name = "Role")
+ @Table(name = "ROLE_TABLE")
+ public static class Role {
+
+ @Id
+ private Long id;
+ private String code;
+
+ public Role() {
+ }
+
+ public Role(Long id, String code) {
+ this.id = id;
+ this.code = code;
+ }
+
+ public Object getId() {
+ return id;
+ }
+
+ public String getCode() {
+ return code;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if ( o == null || getClass() != o.getClass() ) {
+ return false;
+ }
+ Role role = (Role) o;
+ return Objects.equals( id, role.id ) && Objects.equals( code, role.code );
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash( id, code );
+ }
+
+ @Override
+ public String toString() {
+ return "Role{" + code + '}';
+ }
+ }
+}
diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToManyMergeTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToManyMergeTest.java
new file mode 100644
index 000000000..76d7f7732
--- /dev/null
+++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/OneToManyMergeTest.java
@@ -0,0 +1,227 @@
+/* Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright: Red Hat Inc. and Hibernate Authors
+ */
+package org.hibernate.reactive;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import io.vertx.junit5.Timeout;
+import io.vertx.junit5.VertxTestContext;
+import jakarta.persistence.Entity;
+import jakarta.persistence.FetchType;
+import jakarta.persistence.Id;
+import jakarta.persistence.OneToMany;
+import jakarta.persistence.Table;
+
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static org.assertj.core.api.Assertions.assertThat;
+
+@Timeout(value = 2, timeUnit = MINUTES)
+public class OneToManyMergeTest extends BaseReactiveTest {
+
+ private final static Long USER_ID = 1L;
+ private final static Long ADMIN_ROLE_ID = 2L;
+ private final static Long USER_ROLE_ID = 3L;
+ private final static String UPDATED_FIRSTNAME = "UPDATED FIRSTNAME";
+ private final static String UPDATED_LASTNAME = "UPDATED LASTNAME";
+
+ @Override
+ protected Collection> annotatedEntities() {
+ return List.of( User.class, Role.class );
+ }
+
+ @BeforeEach
+ public void populateDb(VertxTestContext context) {
+ Role adminRole = new Role( ADMIN_ROLE_ID, "admin" );
+ Role userRole = new Role( USER_ROLE_ID, "user" );
+ User user = new User( USER_ID, "first", "last", adminRole );
+ test(
+ context, getMutinySessionFactory()
+ .withTransaction( s -> s.persistAll( user, adminRole, userRole ) )
+ );
+ }
+
+ @Test
+ public void testMerge(VertxTestContext context) {
+ test(
+ context, getMutinySessionFactory()
+ .withTransaction( s -> s.find( User.class, USER_ID ) )
+ .chain( user -> getMutinySessionFactory()
+ .withTransaction( s -> s
+ .createQuery( "FROM Role", Role.class )
+ .getResultList() )
+ .map( roles -> {
+ user.getRoles().clear();
+ user.getRoles().addAll( roles );
+ user.setFirstname( UPDATED_FIRSTNAME );
+ user.setLastname( UPDATED_LASTNAME );
+ return user;
+ } )
+ )
+ .chain( user -> {
+ assertThat( user.getFirstname() ).isEqualTo( UPDATED_FIRSTNAME );
+ assertThat( user.getLastname() ).isEqualTo( UPDATED_LASTNAME );
+ assertThat( user.getRoles() ).hasSize( 2 );
+ return getMutinySessionFactory()
+ .withTransaction( s -> s.merge( user ) );
+ }
+ )
+ .chain( v -> getMutinySessionFactory()
+ .withTransaction( s -> s.find( User.class, USER_ID ) )
+ )
+ .invoke( user -> {
+ Role adminRole = new Role( ADMIN_ROLE_ID, "admin" );
+ Role userRole = new Role( USER_ROLE_ID, "user" );
+ assertThat( user.getFirstname() ).isEqualTo( UPDATED_FIRSTNAME );
+ assertThat( user.getLastname() ).isEqualTo( UPDATED_LASTNAME );
+ assertThat( user.getRoles() ).containsExactlyInAnyOrder(
+ adminRole,
+ userRole
+ );
+ }
+ )
+ );
+ }
+
+ @Test
+ public void testMergeRemovingCollectionElements(VertxTestContext context) {
+ test(
+ context, getMutinySessionFactory()
+ .withTransaction( s -> s.find( User.class, USER_ID ) )
+ .chain( user -> getMutinySessionFactory()
+ .withTransaction( s -> s
+ .createQuery( "FROM Role", Role.class )
+ .getResultList() )
+ .map( roles -> {
+ user.clearRoles();
+ user.setFirstname( UPDATED_FIRSTNAME );
+ user.setLastname( UPDATED_LASTNAME );
+ return user;
+ } )
+ )
+ .chain( user -> {
+ assertThat( user.getFirstname() ).isEqualTo( UPDATED_FIRSTNAME );
+ assertThat( user.getLastname() ).isEqualTo( UPDATED_LASTNAME );
+ assertThat( user.getRoles() ).isNull();
+ return getMutinySessionFactory()
+ .withTransaction( s -> s.merge( user ) );
+ }
+ )
+ .chain( v -> getMutinySessionFactory()
+ .withTransaction( s -> s.find( User.class, USER_ID ) )
+ )
+ .invoke( user -> {
+ assertThat( user.getFirstname() ).isEqualTo( UPDATED_FIRSTNAME );
+ assertThat( user.getLastname() ).isEqualTo( UPDATED_LASTNAME );
+ assertThat( user.getRoles() ).isNullOrEmpty();
+ }
+ )
+ );
+ }
+
+ @Entity(name = "User")
+ @Table(name = "USER_TABLE")
+ public static class User {
+
+ @Id
+ private Long id;
+
+ private String firstname;
+
+ private String lastname;
+
+ @OneToMany(fetch = FetchType.EAGER)
+ private List roles;
+
+ public User() {
+ }
+
+ public User(Long id, String firstname, String lastname, Role... roles) {
+ this.id = id;
+ this.firstname = firstname;
+ this.lastname = lastname;
+ this.roles = List.of( roles );
+ }
+
+ public User(Long id, String firstname, String lastname) {
+ this.id = id;
+ this.firstname = firstname;
+ this.lastname = lastname;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public String getFirstname() {
+ return firstname;
+ }
+
+ public void setFirstname(String firstname) {
+ this.firstname = firstname;
+ }
+
+ public String getLastname() {
+ return lastname;
+ }
+
+ public void setLastname(String lastname) {
+ this.lastname = lastname;
+ }
+
+ public List getRoles() {
+ return roles;
+ }
+
+ public void clearRoles() {
+ this.roles = null;
+ }
+ }
+
+ @Entity(name = "Role")
+ @Table(name = "ROLE_TABLE")
+ public static class Role {
+
+ @Id
+ private Long id;
+ private String code;
+
+ public Role() {
+ }
+
+ public Role(Long id, String code) {
+ this.id = id;
+ this.code = code;
+ }
+
+ public Object getId() {
+ return id;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if ( o == null || getClass() != o.getClass() ) {
+ return false;
+ }
+ Role role = (Role) o;
+ return Objects.equals( id, role.id ) && Objects.equals( code, role.code );
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash( id, code );
+ }
+
+ @Override
+ public String toString() {
+ return "Role{" + code + '}';
+ }
+ }
+}
From 5ca4652d747c3f04908d8a1248a0921f86c3cadf Mon Sep 17 00:00:00 2001
From: Davide D'Alto
Date: Mon, 20 Jan 2025 11:57:06 +0100
Subject: [PATCH 3/4] [#2006] Fix EntityTypes#loadByUniqueKey
It's using a `.thenApply` instead of a `.thenCompose`.
---
.../reactive/engine/impl/EntityTypes.java | 47 +++----------------
1 file changed, 7 insertions(+), 40 deletions(-)
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/EntityTypes.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/EntityTypes.java
index 722ba4a78..d5f37dba3 100644
--- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/EntityTypes.java
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/EntityTypes.java
@@ -138,7 +138,7 @@ static CompletionStage loadByUniqueKey(
else {
return persister
.reactiveLoadByUniqueKey( uniqueKeyPropertyName, key, session )
- .thenApply( ukResult -> loadHibernateProxyEntity( ukResult, session )
+ .thenCompose( ukResult -> loadHibernateProxyEntity( ukResult, session )
.thenApply( targetUK -> {
persistenceContext.addEntity( euk, targetUK );
return targetUK;
@@ -364,9 +364,9 @@ private static CompletionStage getIdentifierFromHibernateProxy(
if ( type.isEntityIdentifierMapping() ) {
propertyValue = getIdentifier( (EntityType) type, propertyValue, (SessionImplementor) session );
}
- return completedFuture( propertyValue );
+ return propertyValue;
}
- return nullFuture();
+ return null;
} );
}
@@ -409,15 +409,7 @@ else if ( types[i] instanceof CollectionType ) {
session,
owner,
copyCache
- ).thenCompose( copy -> {
- if ( copy instanceof CompletionStage ) {
- return ( (CompletionStage>) copy ).thenAccept( nonStageCopy -> copied[i] = nonStageCopy );
- }
- else {
- copied[i] = copy;
- return voidFuture();
- }
- } );
+ ).thenAccept( copy -> copied[i] = copy );
}
else if ( types[i] instanceof EntityType ) {
return replace(
@@ -427,16 +419,7 @@ else if ( types[i] instanceof EntityType ) {
session,
owner,
copyCache
- ).thenCompose( copy -> {
- if ( copy instanceof CompletionStage ) {
- return ( (CompletionStage>) copy )
- .thenAccept( nonStageCopy -> copied[i] = nonStageCopy );
- }
- else {
- copied[i] = copy;
- return voidFuture();
- }
- } );
+ ).thenAccept( copy -> copied[i] = copy );
}
else {
final Type type = types[i];
@@ -474,15 +457,7 @@ else if ( types[i] instanceof CollectionType ) {
owner,
copyCache,
foreignKeyDirection
- ).thenCompose( copy -> {
- if ( copy instanceof CompletionStage ) {
- return ( (CompletionStage>) copy ).thenAccept( nonStageCopy -> copied[i] = nonStageCopy );
- }
- else {
- copied[i] = copy;
- return voidFuture();
- }
- } );
+ ).thenAccept( copy -> copied[i] = copy );
}
else if ( types[i] instanceof EntityType ) {
return replace(
@@ -493,15 +468,7 @@ else if ( types[i] instanceof EntityType ) {
owner,
copyCache,
foreignKeyDirection
- ).thenCompose( copy -> {
- if ( copy instanceof CompletionStage ) {
- return ( (CompletionStage>) copy ).thenAccept( nonStageCopy -> copied[i] = nonStageCopy );
- }
- else {
- copied[i] = copy;
- return voidFuture();
- }
- } );
+ ).thenAccept( copy -> copied[i] = copy );
}
else {
copied[i] = types[i].replace(
From 04b5fccca6718157527e1bc9fadbbf024dcc338b Mon Sep 17 00:00:00 2001
From: Davide D'Alto
Date: Mon, 20 Jan 2025 12:03:22 +0100
Subject: [PATCH 4/4] [#2006] Small refactoring and clean ups
---
.../reactive/engine/impl/CollectionTypes.java | 96 +++++++------------
.../reactive/engine/impl/EntityTypes.java | 16 ++--
2 files changed, 41 insertions(+), 71 deletions(-)
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CollectionTypes.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CollectionTypes.java
index 99e227cbc..4bc488bb6 100644
--- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CollectionTypes.java
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/CollectionTypes.java
@@ -42,7 +42,6 @@
import static org.hibernate.pretty.MessageHelper.collectionInfoString;
import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture;
import static org.hibernate.reactive.util.impl.CompletionStages.loop;
-import static org.hibernate.reactive.util.impl.CompletionStages.nullFuture;
import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture;
/**
@@ -81,10 +80,10 @@ public static CompletionStage replace(
Object owner,
Map copyCache) throws HibernateException {
if ( original == null ) {
- return replaceNullOriginal( target, session );
+ return completedFuture( replaceNullOriginal( target, session ) );
}
else if ( !Hibernate.isInitialized( original ) ) {
- return replaceUninitializedOriginal( type, original, target, session, copyCache );
+ return completedFuture( replaceUninitializedOriginal( type, original, target, session, copyCache ) );
}
else {
return replaceOriginal( type, original, target, session, owner, copyCache );
@@ -92,23 +91,22 @@ else if ( !Hibernate.isInitialized( original ) ) {
}
// todo: make org.hibernate.type.CollectionType#replaceNullOriginal public ?
-
/**
* @see CollectionType#replaceNullOriginal(Object, SharedSessionContractImplementor)
*/
- private static CompletionStage replaceNullOriginal(
+ private static Object replaceNullOriginal(
Object target,
SessionImplementor session) {
if ( target == null ) {
- return nullFuture();
+ return null;
}
else if ( target instanceof Collection> collection ) {
collection.clear();
- return completedFuture( collection );
+ return collection;
}
else if ( target instanceof Map, ?> map ) {
map.clear();
- return completedFuture( map );
+ return map;
}
else {
final PersistenceContext persistenceContext = session.getPersistenceContext();
@@ -124,18 +122,15 @@ else if ( target instanceof Map, ?> map ) {
arrayHolder.endRead();
arrayHolder.dirty();
persistenceContext.addCollectionHolder( collectionHolder );
- return completedFuture( arrayHolder.getArray() );
+ return arrayHolder.getArray();
}
}
}
- return nullFuture();
+ return null;
}
// todo: make org.hibernate.type.CollectionType#replaceUninitializedOriginal public
- /**
- * @see CollectionType#replaceNullOriginal(Object, SharedSessionContractImplementor)
- */
- private static CompletionStage replaceUninitializedOriginal(
+ private static Object replaceUninitializedOriginal(
CollectionType type,
Object original,
Object target,
@@ -161,7 +156,7 @@ private static CompletionStage replaceUninitializedOriginal(
collectionInfoString( type.getRole(), persistentCollection.getKey() ) );
}
}
- return completedFuture( target );
+ return target;
}
/**
@@ -193,11 +188,11 @@ private static CompletionStage replaceOriginal(
//TODO: this is a little inefficient, don't need to do a whole
// deep replaceElements() call
return replaceElements( type, result, target, owner, copyCache, session )
- .thenCompose( unused -> {
+ .thenApply( unused -> {
if ( wasClean ) {
( (PersistentCollection>) target ).clearDirty();
}
- return completedFuture( target );
+ return target;
} );
}
else {
@@ -291,10 +286,8 @@ private static CompletionStage replaceMapTypeElements(
Object owner,
Map copyCache,
SessionImplementor session) {
- final CollectionPersister persister =
- session.getFactory().getRuntimeMetamodels().getMappingMetamodel()
- .getCollectionDescriptor( type.getRole() );
-
+ final CollectionPersister persister = session.getFactory().getRuntimeMetamodels()
+ .getMappingMetamodel().getCollectionDescriptor( type.getRole() );
final Map result = target;
result.clear();
@@ -302,15 +295,13 @@ private static CompletionStage replaceMapTypeElements(
original.entrySet(), entry -> {
final Map.Entry me = entry;
return getReplace( persister.getIndexType(), me.getKey(), owner, session, copyCache )
- .thenCompose( key ->
- getReplace(
- persister.getElementType(),
- me.getValue(),
- owner,
- session,
- copyCache
- ).thenAccept( value ->
- result.put( key, value ) )
+ .thenCompose( key -> getReplace(
+ persister.getElementType(),
+ me.getValue(),
+ owner,
+ session,
+ copyCache
+ ).thenAccept( value -> result.put( key, value ) )
);
}
).thenApply( unused -> result );
@@ -335,14 +326,11 @@ private static CompletionStage replaceArrayTypeElements(
final Type elemType = type.getElementType( session.getFactory() );
return loop(
- 0, length, i -> {
- return getReplace( elemType, Array.get( original, i ), owner, session, copyCache )
- .thenApply( o -> {
- Array.set( result, i, o );
- return result;
- }
- );
- }
+ 0, length, i -> getReplace( elemType, Array.get( original, i ), owner, session, copyCache )
+ .thenApply( o -> {
+ Array.set( result, i, o );
+ return result;
+ } )
).thenApply( unused -> result );
}
@@ -384,8 +372,7 @@ private static CompletionStage preserveSnapshot(
final CollectionEntry ce = session.getPersistenceContextInternal().getCollectionEntry( result );
if ( ce != null ) {
return createSnapshot( original, result, elemType, owner, copyCache, session )
- .thenAccept( serializable ->
- ce.resetStoredSnapshot( result, serializable ) );
+ .thenAccept( serializable -> ce.resetStoredSnapshot( result, serializable ) );
}
return voidFuture();
}
@@ -426,14 +413,8 @@ private static CompletionStage createArraySnapshot(
Map copyCache,
SessionImplementor session) {
return loop(
- 0, array.length,
- i ->
- getReplace( elemType, array[i], owner, session, copyCache )
- .thenCompose( o -> {
- array[i] = o;
- return voidFuture();
- }
- )
+ 0, array.length, i -> getReplace( elemType, array[i], owner, session, copyCache )
+ .thenAccept( o -> array[i] = o )
).thenApply( unused -> array );
}
@@ -459,10 +440,9 @@ private static CompletionStage createMapSnapshot(
return loop(
map.entrySet(), entry ->
getReplace( elemType, entry.getValue(), resultSnapshot, owner, session, copyCache )
- .thenCompose( newValue -> {
+ .thenAccept( newValue -> {
final Object key = entry.getKey();
targetMap.put( key == entry.getValue() ? newValue : key, newValue );
- return voidFuture();
} )
).thenApply( v -> (Serializable) targetMap );
}
@@ -478,12 +458,8 @@ private static CompletionStage createListSnapshot(
SessionImplementor session) {
final ArrayList targetList = new ArrayList<>( list.size() );
return loop(
- list, obj ->
- getReplace( elemType, obj, owner, session, copyCache )
- .thenCompose( o -> {
- targetList.add( o );
- return voidFuture();
- } )
+ list, obj -> getReplace( elemType, obj, owner, session, copyCache )
+ .thenAccept( targetList::add )
).thenApply( unused -> targetList );
}
@@ -498,10 +474,8 @@ private static Object instantiateResultIfNecessary(CollectionType type, Object o
return target == null
|| target == original
|| target == UNFETCHED_PROPERTY
- || target instanceof PersistentCollection> collection && collection.isWrapper( original ) ?
- type.instantiate( -1 ) :
- target;
+ || target instanceof PersistentCollection> collection && collection.isWrapper( original )
+ ? type.instantiate( -1 )
+ : target;
}
-
-
}
diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/EntityTypes.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/EntityTypes.java
index d5f37dba3..fb1c29fe4 100644
--- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/EntityTypes.java
+++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/impl/EntityTypes.java
@@ -160,10 +160,7 @@ public static CompletionStage replace(
final Map copyCache) {
Object[] copied = new Object[original.length];
return loop(
- 0, types.length,
- i ->
- replace( original, target, types, session, owner, copyCache, i, copied )
-
+ 0, types.length, i -> replace( original, target, types, session, owner, copyCache, i, copied )
).thenApply( v -> copied );
}
@@ -181,9 +178,7 @@ public static CompletionStage replace(
Object[] copied = new Object[original.length];
return loop(
0, types.length,
- i ->
- replace( original, target, types, session, owner, copyCache, foreignKeyDirection, i, copied )
-
+ i -> replace( original, target, types, session, owner, copyCache, foreignKeyDirection, i, copied )
).thenApply( v -> copied );
}
@@ -274,15 +269,16 @@ private static CompletionStage resolveIdOrUniqueKey(
// as a ComponentType. In the case that the entity is unfetched, we need to
// explicitly fetch it here before calling replace(). (Note that in Hibernate
// ORM this is unnecessary due to transparent lazy fetching.)
- return ( (ReactiveSessionImpl) session ).reactiveFetch( id, true )
+ return ( (ReactiveSessionImpl) session )
+ .reactiveFetch( id, true )
.thenCompose( fetched -> {
- Object idOrUniqueKey = entityType.getIdentifierOrUniqueKeyType( session.getFactory() )
+ Object idOrUniqueKey = entityType
+ .getIdentifierOrUniqueKeyType( session.getFactory() )
.replace( fetched, null, session, owner, copyCache );
if ( idOrUniqueKey instanceof CompletionStage ) {
return ( (CompletionStage>) idOrUniqueKey )
.thenCompose( key -> resolve( entityType, key, owner, session ) );
}
-
return resolve( entityType, idOrUniqueKey, owner, session );
} );
} );