From 77f815e3fec1b96f8d709f9ed5c1def4d269116a Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Tue, 18 Feb 2025 14:34:10 +0100 Subject: [PATCH 1/7] Changes for next ORM * NullabilityCheck has changed * Remove ReactiveQueryImplementor#setOptional* --- .../event/impl/DefaultReactiveDeleteEventListener.java | 7 ++----- .../hibernate/reactive/query/ReactiveQueryImplementor.java | 7 ------- 2 files changed, 2 insertions(+), 12 deletions(-) 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 664b09da3..f490d5366 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 @@ -436,11 +436,8 @@ protected CompletionStage deleteEntity( persister ).nullifyTransientReferences( entityEntry.getDeletedState() ) .thenAccept( vv -> { - new Nullability( session ).checkNullability( - entityEntry.getDeletedState(), - persister, - Nullability.NullabilityCheckType.DELETE - ); + new Nullability( session, Nullability.NullabilityCheckType.DELETE ) + .checkNullability( entityEntry.getDeletedState(), persister ); persistenceContext.registerNullifiableEntityKey( key ); final ReactiveActionQueue actionQueue = actionQueue( session ); diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/ReactiveQueryImplementor.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/ReactiveQueryImplementor.java index 7c7588a7c..0354a59e5 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/ReactiveQueryImplementor.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/ReactiveQueryImplementor.java @@ -5,7 +5,6 @@ */ package org.hibernate.reactive.query; -import java.io.Serializable; import java.time.Instant; import java.util.Calendar; import java.util.Collection; @@ -23,12 +22,6 @@ public interface ReactiveQueryImplementor extends ReactiveQuery { - void setOptionalId(Serializable id); - - void setOptionalEntityName(String entityName); - - void setOptionalObject(Object optionalObject); - QueryParameterBindings getParameterBindings(); @Override From 58f06e8f7b9d085c820d03d43200a691910cc27a Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Thu, 20 Feb 2025 16:00:11 +0100 Subject: [PATCH 2/7] Fix warnings in test --- .../JoinedSubclassInheritanceTest.java | 61 +++++++++++-------- 1 file changed, 37 insertions(+), 24 deletions(-) diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/JoinedSubclassInheritanceTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/JoinedSubclassInheritanceTest.java index 2b0e3f9c6..c1333da57 100644 --- a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/JoinedSubclassInheritanceTest.java +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/JoinedSubclassInheritanceTest.java @@ -28,8 +28,8 @@ import jakarta.persistence.TemporalType; import static java.util.concurrent.TimeUnit.MINUTES; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -56,9 +56,8 @@ public void testRootClassViaAssociation(VertxTestContext context) { .thenCompose( v -> openSession() ) .thenCompose( s2 -> s2.find( Author.class, author.getId() ) ) .thenAccept( auth -> { - assertNotNull( auth ); - assertEquals( author, auth ); - assertEquals( book.getTitle(), auth.getBook().getTitle() ); + assertThat( auth ).isEqualTo( author ); + assertThat( auth.getBook().getTitle() ).isEqualTo( book.getTitle() ); } ) ) ); @@ -77,9 +76,8 @@ public void testSubclassViaAssociation(VertxTestContext context) { .thenCompose( v -> s.flush() ) .thenCompose( v -> s.find( Author.class, author.getId() ) ) .thenAccept( auth -> { - assertNotNull( auth ); - assertEquals( author, auth ); - assertEquals( book.getTitle(), auth.getBook().getTitle() ); + assertThat( auth ).isEqualTo( author ); + assertThat( auth.getBook().getTitle() ).isEqualTo( book.getTitle() ); } ) ) ); @@ -87,7 +85,6 @@ public void testSubclassViaAssociation(VertxTestContext context) { @Test public void testRootClassViaFind(VertxTestContext context) { - final Book novel = new Book( 6, "The Boy, The Mole, The Fox and The Horse", new Date()); final Author author = new Author( "Charlie Mackesy", novel ); @@ -99,9 +96,8 @@ public void testRootClassViaFind(VertxTestContext context) { .thenCompose( v -> openSession() .thenCompose( s -> s.find(Book.class, 6) ) ) .thenAccept(book -> { - assertNotNull(book); - assertFalse(book instanceof SpellBook); - assertEquals(book.getTitle(), "The Boy, The Mole, The Fox and The Horse"); + assertThat( book ).isNotInstanceOf( SpellBook.class ); + assertThat( book.getTitle() ).isEqualTo( "The Boy, The Mole, The Fox and The Horse" ); })); } @@ -126,26 +122,33 @@ public void testSubclassViaFind(VertxTestContext context) { @Test public void testQueryUpdate(VertxTestContext context) { final SpellBook spells = new SpellBook( 6, "Necronomicon", true, new Date() ); -// final Author author = new Author( "Abdul Alhazred", spells ); - test( context, + test( + context, openSession() - .thenCompose( s -> s.persist(spells).thenCompose( v -> s.flush() ) ) + .thenCompose( s -> s.persist( spells ).thenCompose( v -> s.flush() ) ) .thenCompose( vv -> openSession() - .thenCompose( s -> s.withTransaction( t -> s.createMutationQuery("update SpellBook set title='x' where forbidden=false").executeUpdate() ) - .thenCompose( v -> s.withTransaction( t -> s.createMutationQuery("update SpellBook set forbidden=false where title='Necronomicon'").executeUpdate() ) ) - .thenCompose( v -> s.withTransaction( t -> s.createMutationQuery("update Book set title=title||' II' where title='Necronomicon'").executeUpdate() ) ) - .thenCompose( v -> s.find(Book.class, 6) ) + .thenCompose( s -> s.withTransaction( t -> s + .createMutationQuery( "update SpellBook set title='x' where forbidden=false" ) + .executeUpdate() ) + .thenCompose( v -> s.withTransaction( t -> s + .createMutationQuery( "update SpellBook set forbidden=false where title='Necronomicon'" ) + .executeUpdate() ) ) + .thenCompose( v -> s.withTransaction( t -> s + .createMutationQuery( "update Book set title=title||' II' where title='Necronomicon'" ) + .executeUpdate() ) ) + .thenCompose( v -> s.find( Book.class, 6 ) ) .thenAccept( book -> { - assertNotNull(book); - assertTrue(book instanceof SpellBook); - assertEquals(book.getTitle(), "Necronomicon II"); + assertThat( book ).isInstanceOf( SpellBook.class ); + assertThat( book.getTitle() ).isEqualTo( "Necronomicon II" ); } ) - ) ) + ) ) .thenCompose( vv -> openSession() - .thenCompose( s -> s.withTransaction( t -> s.createMutationQuery("delete Book where title='Necronomicon II'").executeUpdate() ) ) + .thenCompose( s -> s.withTransaction( t -> s + .createMutationQuery( "delete Book where title='Necronomicon II'" ) + .executeUpdate() ) ) .thenCompose( v -> openSession() ) - .thenCompose( s -> s.find(Book.class, 6) ) + .thenCompose( s -> s.find( Book.class, 6 ) ) .thenAccept( Assertions::assertNull ) ) ); @@ -207,6 +210,11 @@ public SpellBook(Integer id, String title, boolean forbidden, Date published) { public boolean getForbidden() { return forbidden; } + + @Override + public String toString() { + return "SpellBook{" + super.toString() + ",forbidden=" + forbidden + '}'; + } } @Entity(name="Book") @@ -268,6 +276,11 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash( title ); } + + @Override + public String toString() { + return id + ", " + title + ", " + published; + } } @Entity(name = "Author") From 429f6c2510726654431ef6ce8ab2c6607df8ce4e Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Fri, 13 Dec 2024 07:46:51 +0100 Subject: [PATCH 3/7] [#1930] Change properties to test ORM 7.0 snapshots --- gradle.properties | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gradle.properties b/gradle.properties index 299ccf3b9..0b775dbd9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -29,22 +29,22 @@ org.gradle.java.installations.auto-download=false #db = MSSQL # Enable the SonatypeOS maven repository (mainly for Vert.x snapshots) when present (value ignored) -#enableSonatypeOpenSourceSnapshotsRep = true +enableSonatypeOpenSourceSnapshotsRep = true # Enable the maven local repository (for local development when needed) when present (value ignored) #enableMavenLocalRepo = true # The default Hibernate ORM version (override using `-PhibernateOrmVersion=the.version.you.want`) -hibernateOrmVersion = 7.0.0.Beta4 +hibernateOrmVersion = 7.0.0-SNAPSHOT # Override default Hibernate ORM Gradle plugin version # Using the stable version because I don't know how to configure the build to download the snapshot version from # a remote repository -#hibernateOrmGradlePluginVersion = 7.0.0.Beta4 +hibernateOrmGradlePluginVersion = 7.0.0.Beta4 # If set to true, skip Hibernate ORM version parsing (default is true, if set to null) # this is required when using intervals or weird versions or the build will fail -#skipOrmVersionParsing = true +skipOrmVersionParsing = true # Override default Vert.x Sql client version #vertxSqlClientVersion = 4.5.13-SNAPSHOT From a4adc1428948665c49f8a4776dae1ade85b1ec41 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Thu, 20 Feb 2025 12:34:16 +0100 Subject: [PATCH 4/7] fix --- .../mutiny/impl/MutinySessionImpl.java | 2 +- .../sql/internal/ReactiveNativeQueryImpl.java | 23 ++-- .../session/ReactiveQueryProducer.java | 2 + .../session/impl/ReactiveSessionImpl.java | 103 ++++++++++++++---- .../impl/ReactiveStatelessSessionImpl.java | 90 ++++++++++++--- .../reactive/stage/impl/StageSessionImpl.java | 31 +++--- .../stage/impl/StageStatelessSessionImpl.java | 2 +- 7 files changed, 192 insertions(+), 61 deletions(-) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySessionImpl.java index 929987b55..87cf50bb0 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySessionImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/mutiny/impl/MutinySessionImpl.java @@ -173,7 +173,7 @@ public MutationQuery createQuery(CriteriaDelete criteriaDelete) { @Override public Query createNamedQuery(String queryName) { - return new MutinyQueryImpl<>( delegate.createReactiveNamedQuery( queryName, null ), factory ); + return new MutinyQueryImpl<>( delegate.createReactiveNamedQuery( queryName ), factory ); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeQueryImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeQueryImpl.java index 6d162882a..ac8f4e9b4 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeQueryImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sql/internal/ReactiveNativeQueryImpl.java @@ -26,7 +26,6 @@ import org.hibernate.graph.GraphSemantic; import org.hibernate.graph.RootGraph; import org.hibernate.graph.spi.RootGraphImplementor; -import org.hibernate.internal.AbstractSharedSessionContract; import org.hibernate.metamodel.model.domain.BasicDomainType; import org.hibernate.query.BindableType; import org.hibernate.query.Order; @@ -65,8 +64,18 @@ public class ReactiveNativeQueryImpl extends NativeQueryImpl private final ReactiveAbstractSelectionQuery selectionQueryDelegate; - public ReactiveNativeQueryImpl(String memento, SharedSessionContractImplementor session) { - super( memento, session ); + public ReactiveNativeQueryImpl(String sql, SharedSessionContractImplementor session) { + super( sql, null, session ); + this.selectionQueryDelegate = createSelectionQueryDelegate( session ); + } + + public ReactiveNativeQueryImpl(String sql, Class resultClass, SharedSessionContractImplementor session) { + super( sql, resultClass, session ); + this.selectionQueryDelegate = createSelectionQueryDelegate( session ); + } + + public ReactiveNativeQueryImpl(String sql, NamedResultSetMappingMemento resultSetMappingMemento, Class resultClass, SharedSessionContractImplementor session) { + super( sql, resultSetMappingMemento, resultClass, session); this.selectionQueryDelegate = createSelectionQueryDelegate( session ); } @@ -91,14 +100,6 @@ public ReactiveNativeQueryImpl( this.selectionQueryDelegate = createSelectionQueryDelegate( session ); } - public ReactiveNativeQueryImpl( - String sqlString, - NamedResultSetMappingMemento resultSetMappingMemento, - AbstractSharedSessionContract session) { - super( sqlString, resultSetMappingMemento, session ); - this.selectionQueryDelegate = createSelectionQueryDelegate( session ); - } - // Convenient for passing parameters to ReactiveAbstractSelectionQuery using method reference private T getNull() { return null; diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/ReactiveQueryProducer.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/ReactiveQueryProducer.java index 25a7402ef..a3b667fea 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/ReactiveQueryProducer.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/session/ReactiveQueryProducer.java @@ -57,6 +57,8 @@ public interface ReactiveQueryProducer extends ReactiveConnectionSupplier { ReactiveQuery createReactiveQuery(String queryString, Class resultType); + ReactiveQueryImplementor createReactiveNamedQuery(String queryString); + ReactiveQueryImplementor createReactiveNamedQuery(String queryString, Class resultType); ReactiveNativeQuery createReactiveNativeQuery(String sqlString); 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 fc8ee8ae6..af8a41aba 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 @@ -72,13 +72,16 @@ import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.LazyInitializer; import org.hibernate.query.IllegalMutationQueryException; +import org.hibernate.query.UnknownNamedQueryException; import org.hibernate.query.criteria.JpaCriteriaInsert; import org.hibernate.query.hql.spi.SqmQueryImplementor; import org.hibernate.query.named.NamedResultSetMappingMemento; import org.hibernate.query.spi.HqlInterpretation; import org.hibernate.query.spi.QueryImplementor; +import org.hibernate.query.sql.spi.NamedNativeQueryMemento; import org.hibernate.query.sql.spi.NativeQueryImplementor; import org.hibernate.query.sqm.internal.SqmUtil; +import org.hibernate.query.sqm.spi.NamedSqmQueryMemento; import org.hibernate.query.sqm.tree.SqmStatement; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.insert.SqmInsertStatement; @@ -131,7 +134,6 @@ import static org.hibernate.engine.spi.NaturalIdResolutions.INVALID_NATURAL_ID_REFERENCE; import static org.hibernate.event.spi.LoadEventListener.IMMEDIATE_LOAD; import static org.hibernate.internal.util.StringHelper.isEmpty; -import static org.hibernate.internal.util.StringHelper.isNotEmpty; import static org.hibernate.proxy.HibernateProxy.extractLazyInitializer; import static org.hibernate.reactive.common.InternalStateAssertions.assertUseOnEventLoop; import static org.hibernate.reactive.persister.entity.impl.ReactiveEntityPersister.forceInitialize; @@ -387,7 +389,7 @@ public ReactiveNativeQueryImplementor createReactiveNativeQuery(String sq delayedAfterCompletion(); try { - final ReactiveNativeQueryImpl query = new ReactiveNativeQueryImpl<>( sqlString, this ); + final ReactiveNativeQueryImpl query = new ReactiveNativeQueryImpl<>( sqlString, mthis ); if ( isEmpty( query.getComment() ) ) { query.setComment( "dynamic native SQL query" ); } @@ -443,14 +445,16 @@ public ReactiveNativeQuery createReactiveNativeQuery(String sqlString, Cl @Override @Deprecated(forRemoval = true) public ReactiveNativeQuery createReactiveNativeQuery(String sqlString, String resultSetMappingName) { + if ( isEmpty( resultSetMappingName ) ) { + throw new IllegalArgumentException( "Result set mapping name was not specified" ); + } + checkOpen(); pulseTransactionCoordinator(); delayedAfterCompletion(); try { - return isNotEmpty( resultSetMappingName ) - ? new ReactiveNativeQueryImpl<>( sqlString, getResultSetMappingMemento( resultSetMappingName ), this ) - : new ReactiveNativeQueryImpl<>( sqlString, this ); + return new ReactiveNativeQueryImpl<>( sqlString, getResultSetMappingMemento( resultSetMappingName ), null, this ); //TODO: why no applyQuerySettingsAndHints( query ); ??? } catch (RuntimeException he) { @@ -497,9 +501,78 @@ private ReactiveSelectionQuery createSelectionQuery(String hql, Class return query; } + @Override + public ReactiveQueryImplementor createReactiveNamedQuery(String name) { + checksBeforeQueryCreation(); + try { + return (ReactiveQueryImplementor) buildNamedQuery( + name, + this::createSqmQueryImplementor, + this::createNativeQueryImplementor + ); + } + catch (RuntimeException e) { + throw convertNamedQueryException( e ); + } + } + @Override public ReactiveQueryImplementor createReactiveNamedQuery(String name, Class resultType) { - return (ReactiveQueryImplementor) buildNamedQuery( name, resultType ); + checksBeforeQueryCreation(); + if ( resultType == null ) { + throw new IllegalArgumentException( "Result class is null" ); + } + try { + return buildNamedQuery( + name, + memento -> createReactiveSqmQueryImplementor( resultType, memento ), + memento -> createReactiveNativeQueryImplementor( resultType, memento ) + ); + } + catch (RuntimeException e) { + throw convertNamedQueryException( e ); + } + } + + private void checksBeforeQueryCreation() { + checkOpen(); + checkTransactionSynchStatus(); + } + + protected ReactiveNativeQueryImpl createReactiveNativeQueryImplementor(Class resultType, NamedNativeQueryMemento memento) { + final NativeQueryImplementor query = memento.toQuery(this, resultType ); + if ( isEmpty( query.getComment() ) ) { + query.setComment( "dynamic native SQL query" ); + } + applyQuerySettingsAndHints( query ); + return (ReactiveNativeQueryImpl) query; + } + + protected ReactiveQuerySqmImpl createReactiveSqmQueryImplementor(Class resultType, NamedSqmQueryMemento memento) { + final SqmQueryImplementor query = memento.toQuery( this, resultType ); + if ( isEmpty( query.getComment() ) ) { + query.setComment( "dynamic query" ); + } + applyQuerySettingsAndHints( query ); + if ( memento.getLockOptions() != null ) { + query.setLockOptions( memento.getLockOptions() ); + } + return (ReactiveQuerySqmImpl) query; + } + + private RuntimeException convertNamedQueryException(RuntimeException e) { + if ( e instanceof UnknownNamedQueryException ) { + // JPA expects this to mark the transaction for rollback only + getTransactionCoordinator().getTransactionDriverControl().markRollbackOnly(); + // it also expects an IllegalArgumentException, so wrap UnknownNamedQueryException + return new IllegalArgumentException( e.getMessage(), e ); + } + else if ( e instanceof IllegalArgumentException ) { + return e; + } + else { + return getExceptionConverter().convert( e ); + } } @Override @@ -584,7 +657,7 @@ public ReactiveNativeQuery createReactiveNativeQuery(String queryString, delayedAfterCompletion(); try { - final ReactiveNativeQueryImpl query = new ReactiveNativeQueryImpl<>( queryString, this ); + final ReactiveNativeQueryImpl query = new ReactiveNativeQueryImpl<>( queryString, null, this ); addAffectedEntities( affectedEntities, query ); if ( isEmpty( query.getComment() ) ) { query.setComment( "dynamic native SQL query" ); @@ -622,12 +695,11 @@ public ReactiveNativeQueryImpl createReactiveNativeQuery(String queryStri checkOpen(); pulseTransactionCoordinator(); delayedAfterCompletion(); - + // Should we throw an exception? + NamedResultSetMappingMemento memento = resultSetMapping == null ? null : getResultSetMappingMemento( resultSetMapping.getName() ); try { // Same approach as AbstractSharedSessionContract#createNativeQuery(String, String) - final ReactiveNativeQueryImpl nativeQuery = resultSetMapping != null - ? new ReactiveNativeQueryImpl<>( queryString, getResultSetMappingMemento( resultSetMapping.getName() ), this ) - : new ReactiveNativeQueryImpl<>( queryString, this ); + final ReactiveNativeQueryImpl nativeQuery = new ReactiveNativeQueryImpl<>( queryString, memento, null, this ); applyQuerySettingsAndHints( nativeQuery ); return nativeQuery; } @@ -652,20 +724,13 @@ public ResultSetMapping getResultSetMapping(Class resultType, String m if ( mapping == null ) { throw new IllegalArgumentException( "result set mapping does not exist: " + mappingName ); } -// -// ResultSetMappingImpl resultSetMapping = new ResultSetMappingImpl( "impl" ); -// if ( resultType != null ) { -// Class mappedResultType = resultSetMapping.; -// if ( !resultType.equals( mappedResultType ) ) { -// throw new IllegalArgumentException( "incorrect result type for result set mapping: " + mappingName + " has type " + mappedResultType.getName() ); -// } -// } return new ResultSetMapping<>() { @Override public String getName() { return mappingName; } + @Override public Class getResultType() { return resultType; 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 4941dda50..2d3efe4f5 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 @@ -22,6 +22,7 @@ import org.hibernate.cache.spi.access.EntityDataAccess; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.dialect.Dialect; +import org.hibernate.engine.internal.ReactivePersistenceContextAdapter; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.PersistenceContext; @@ -47,10 +48,12 @@ import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.LazyInitializer; import org.hibernate.query.IllegalMutationQueryException; +import org.hibernate.query.UnknownNamedQueryException; 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; import org.hibernate.query.spi.QueryImplementor; import org.hibernate.query.sql.spi.NativeQueryImplementor; @@ -64,7 +67,6 @@ import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; import org.hibernate.reactive.common.AffectedEntities; import org.hibernate.reactive.common.ResultSetMapping; -import org.hibernate.engine.internal.ReactivePersistenceContextAdapter; import org.hibernate.reactive.id.ReactiveIdentifierGenerator; import org.hibernate.reactive.logging.impl.Log; import org.hibernate.reactive.persister.collection.impl.ReactiveCollectionPersister; @@ -905,7 +907,7 @@ public ReactiveNativeQueryImplementor createReactiveNativeQuery(String sq delayedAfterCompletion(); try { - final ReactiveNativeQueryImpl query = new ReactiveNativeQueryImpl<>( sqlString, this ); + final ReactiveNativeQueryImpl query = new ReactiveNativeQueryImpl<>( sqlString, null, this); if ( isEmpty( query.getComment() ) ) { query.setComment( "dynamic native SQL query" ); } @@ -956,9 +958,19 @@ public ReactiveNativeQuery createReactiveNativeQuery(String sqlString, St delayedAfterCompletion(); try { - return isNotEmpty( resultSetMappingName ) - ? new ReactiveNativeQueryImpl<>( sqlString, getResultSetMappingMemento( resultSetMappingName ), this ) - : new ReactiveNativeQueryImpl<>( sqlString, this ); + if ( isNotEmpty( resultSetMappingName ) ) { + final NamedResultSetMappingMemento resultSetMappingMemento = getFactory().getQueryEngine() + .getNamedObjectRepository() + .getResultSetMappingMemento( resultSetMappingName ); + + if ( resultSetMappingMemento == null ) { + throw new HibernateException( "Could not resolve specified result-set mapping name : " + resultSetMappingName ); + } + return new ReactiveNativeQueryImpl<>( sqlString, resultSetMappingMemento, null, this ); + } + else { + return new ReactiveNativeQueryImpl<>( sqlString, this ); + } //TODO: why no applyQuerySettingsAndHints( query ); ??? } catch (RuntimeException he) { @@ -1069,11 +1081,64 @@ public ReactiveMutationQuery createNamedReactiveMutationQuery(String quer ); } + @Override + public ReactiveQueryImplementor createReactiveNamedQuery(String queryName) { + checksBeforeQueryCreation(); + try { + return (ReactiveQueryImplementor) buildNamedQuery( + queryName, + this::createSqmQueryImplementor, + this::createNativeQueryImplementor + ); + } + catch (RuntimeException e) { + throw convertNamedQueryException( e ); + } + } + + @Override + public ReactiveQueryImplementor createReactiveNamedQuery(String queryName, Class resultType) { + checksBeforeQueryCreation(); + if ( resultType == null ) { + throw new IllegalArgumentException( "Result class is null" ); + } + try { + return (ReactiveQueryImplementor) buildNamedQuery( + queryName, + memento -> createSqmQueryImplementor( resultType, memento ), + memento -> createNativeQueryImplementor( resultType, memento ) + ); + } + catch (RuntimeException e) { + throw convertNamedQueryException( e ); + } + } + + private RuntimeException convertNamedQueryException(RuntimeException e) { + if ( e instanceof UnknownNamedQueryException ) { + // JPA expects this to mark the transaction for rollback only + getTransactionCoordinator().getTransactionDriverControl().markRollbackOnly(); + // it also expects an IllegalArgumentException, so wrap UnknownNamedQueryException + return new IllegalArgumentException( e.getMessage(), e ); + } + else if ( e instanceof IllegalArgumentException ) { + return e; + } + else { + return getExceptionConverter().convert( e ); + } + } + @Override public ReactiveSelectionQuery createNamedReactiveSelectionQuery(String queryName, Class expectedResultType) { return (ReactiveSelectionQuery) createNamedSelectionQuery( queryName , expectedResultType ); } + private void checksBeforeQueryCreation() { + checkOpen(); + checkTransactionSynchStatus(); + } + @Override public ReactiveMutationQuery createNativeReactiveMutationQuery(String sqlString) { final ReactiveNativeQueryImplementor query = createReactiveNativeQuery( sqlString ); @@ -1083,11 +1148,6 @@ public ReactiveMutationQuery createNativeReactiveMutationQuery(String sql return query; } - @Override - public ReactiveQueryImplementor createReactiveNamedQuery(String queryName, Class resultType) { - return (ReactiveQueryImplementor) buildNamedQuery( queryName, resultType ); - } - @Override public ReactiveNativeQuery createReactiveNativeQuery(String queryString, AffectedEntities affectedEntities) { checkOpen(); @@ -1131,11 +1191,13 @@ public ReactiveNativeQueryImpl createReactiveNativeQuery(String queryStri pulseTransactionCoordinator(); delayedAfterCompletion(); + if ( resultSetMapping == null ) { + throw new IllegalArgumentException( "Result set mapping was not specified" ); + } + try { - // Same approach as AbstractSharedSessionContract#createNativeQuery(String, String) - final ReactiveNativeQueryImpl nativeQuery = resultSetMapping != null - ? new ReactiveNativeQueryImpl<>( queryString, getResultSetMappingMemento( resultSetMapping.getName() ), this ) - : new ReactiveNativeQueryImpl<>( queryString, this ); + final NamedResultSetMappingMemento memento = getResultSetMappingMemento( resultSetMapping.getName() ); + final ReactiveNativeQueryImpl nativeQuery = new ReactiveNativeQueryImpl<>( queryString, memento, null, this ); applyQuerySettingsAndHints( nativeQuery ); return nativeQuery; } diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSessionImpl.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSessionImpl.java index 78ce3c891..150c2ec96 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSessionImpl.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/stage/impl/StageSessionImpl.java @@ -5,16 +5,11 @@ */ package org.hibernate.reactive.stage.impl; -import jakarta.persistence.CacheRetrieveMode; -import jakarta.persistence.CacheStoreMode; -import jakarta.persistence.EntityGraph; -import jakarta.persistence.FlushModeType; -import jakarta.persistence.LockModeType; -import jakarta.persistence.PersistenceException; -import jakarta.persistence.criteria.CriteriaDelete; -import jakarta.persistence.criteria.CriteriaQuery; -import jakarta.persistence.criteria.CriteriaUpdate; -import jakarta.persistence.metamodel.Attribute; +import java.lang.invoke.MethodHandles; +import java.util.List; +import java.util.concurrent.CompletionStage; +import java.util.function.Function; + import org.hibernate.CacheMode; import org.hibernate.Filter; import org.hibernate.FlushMode; @@ -39,10 +34,16 @@ import org.hibernate.reactive.stage.Stage.Query; import org.hibernate.reactive.stage.Stage.SelectionQuery; -import java.lang.invoke.MethodHandles; -import java.util.List; -import java.util.concurrent.CompletionStage; -import java.util.function.Function; +import jakarta.persistence.CacheRetrieveMode; +import jakarta.persistence.CacheStoreMode; +import jakarta.persistence.EntityGraph; +import jakarta.persistence.FlushModeType; +import jakarta.persistence.LockModeType; +import jakarta.persistence.PersistenceException; +import jakarta.persistence.criteria.CriteriaDelete; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.CriteriaUpdate; +import jakarta.persistence.metamodel.Attribute; import static org.hibernate.reactive.util.impl.CompletionStages.applyToAll; import static org.hibernate.reactive.util.impl.CompletionStages.returnOrRethrow; @@ -556,7 +557,7 @@ public MutationQuery createQuery(CriteriaDelete criteriaDelete) { @Override public Query createNamedQuery(String queryName) { - return new StageQueryImpl<>( delegate.createReactiveNamedQuery( queryName, null ) ); + return new StageQueryImpl<>( delegate.createReactiveNamedQuery( queryName ) ); } @Override 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 aadd76285..c48a7eab8 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 @@ -326,7 +326,7 @@ public Query createNativeQuery(String queryString) { @Override public Query createNamedQuery(String queryName) { - return new StageQueryImpl<>( delegate.createReactiveNamedQuery( queryName, null ) ); + return new StageQueryImpl<>( delegate.createReactiveNamedQuery( queryName ) ); } @Override From d14ea110ebc8613133fc8a5032d0e3eea43f9cf2 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Tue, 4 Mar 2025 10:46:34 +0100 Subject: [PATCH 5/7] fix --- .../impl/MutinyStatelessSessionImpl.java | 2 +- .../ReactiveSimpleDeleteQueryPlan.java | 39 +++++++++---------- .../session/impl/ReactiveSessionImpl.java | 2 +- 3 files changed, 20 insertions(+), 23 deletions(-) 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 d6e8e984e..5605a7911 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 @@ -93,7 +93,7 @@ public Mutiny.MutationQuery createMutationQuery(String queryString) { @Override public Query createNamedQuery(String queryName) { - return new MutinyQueryImpl<>( delegate.createReactiveNamedQuery( queryName, null ), factory ); + return new MutinyQueryImpl<>( delegate.createReactiveNamedQuery( queryName ), factory ); } @Override diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleDeleteQueryPlan.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleDeleteQueryPlan.java index a17d1eb73..eb055a38a 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleDeleteQueryPlan.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/query/sqm/internal/ReactiveSimpleDeleteQueryPlan.java @@ -37,9 +37,7 @@ import org.hibernate.sql.ast.SqlAstTranslator; import org.hibernate.sql.ast.tree.AbstractUpdateOrDeleteStatement; import org.hibernate.sql.ast.tree.MutationStatement; -import org.hibernate.sql.ast.tree.expression.ColumnReference; import org.hibernate.sql.ast.tree.expression.Expression; -import org.hibernate.sql.ast.tree.expression.JdbcLiteral; import org.hibernate.sql.ast.tree.from.MutatingTableReferenceGroupWrapper; import org.hibernate.sql.ast.tree.from.NamedTableReference; import org.hibernate.sql.ast.tree.predicate.InSubQueryPredicate; @@ -95,29 +93,28 @@ protected SqlAstTranslator createTranslato return factory.getJdbcServices() .getJdbcEnvironment() .getSqlAstTranslatorFactory() - .buildMutationTranslator( factory, mutationStatement() ); + .buildMutationTranslator( factory, createDeleteAst() ); } - private MutationStatement mutationStatement() { + // Copy and paste from superclass + private MutationStatement createDeleteAst() { + final MutationStatement ast; if ( entityDescriptor.getSoftDeleteMapping() == null ) { - return sqmInterpretation.getSqlAst(); + ast = sqmInterpretation.getSqlAst(); } - final AbstractUpdateOrDeleteStatement sqlDeleteAst = sqmInterpretation.getSqlAst(); - final NamedTableReference targetTable = sqlDeleteAst.getTargetTable(); - final SoftDeleteMapping columnMapping = getEntityDescriptor().getSoftDeleteMapping(); - final ColumnReference columnReference = new ColumnReference( targetTable, columnMapping ); - //noinspection rawtypes,unchecked - final JdbcLiteral jdbcLiteral = new JdbcLiteral( - columnMapping.getDeletedLiteralValue(), - columnMapping.getJdbcMapping() - ); - final Assignment assignment = new Assignment( columnReference, jdbcLiteral ); - - return new UpdateStatement( - targetTable, - Collections.singletonList( assignment ), - sqlDeleteAst.getRestriction() - ); + else { + final AbstractUpdateOrDeleteStatement sqlDeleteAst = sqmInterpretation.getSqlAst(); + final NamedTableReference targetTable = sqlDeleteAst.getTargetTable(); + final SoftDeleteMapping columnMapping = getEntityDescriptor().getSoftDeleteMapping(); + final Assignment assignment = columnMapping.createSoftDeleteAssignment( targetTable ); + + ast = new UpdateStatement( + targetTable, + Collections.singletonList( assignment ), + sqlDeleteAst.getRestriction() + ); + } + return ast; } @Override 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 af8a41aba..72157e46e 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 @@ -389,7 +389,7 @@ public ReactiveNativeQueryImplementor createReactiveNativeQuery(String sq delayedAfterCompletion(); try { - final ReactiveNativeQueryImpl query = new ReactiveNativeQueryImpl<>( sqlString, mthis ); + final ReactiveNativeQueryImpl query = new ReactiveNativeQueryImpl<>( sqlString, this ); if ( isEmpty( query.getComment() ) ) { query.setComment( "dynamic native SQL query" ); } From 6d4f65e4bcf4aea371e1a551c8634e9010448a2e Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Thu, 6 Mar 2025 16:39:44 +0100 Subject: [PATCH 6/7] [#1867] Error when inserting in batch with joined table inheritance --- .../ReactiveMutationExecutorStandard.java | 89 +++++++++++++++++-- 1 file changed, 82 insertions(+), 7 deletions(-) diff --git a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/jdbc/mutation/internal/ReactiveMutationExecutorStandard.java b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/jdbc/mutation/internal/ReactiveMutationExecutorStandard.java index 4f1d62325..29cfb4f3b 100644 --- a/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/jdbc/mutation/internal/ReactiveMutationExecutorStandard.java +++ b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/engine/jdbc/mutation/internal/ReactiveMutationExecutorStandard.java @@ -5,10 +5,6 @@ */ package org.hibernate.reactive.engine.jdbc.mutation.internal; -import java.lang.invoke.MethodHandles; -import java.sql.SQLException; -import java.util.concurrent.CompletionStage; - import org.hibernate.engine.jdbc.batch.spi.Batch; import org.hibernate.engine.jdbc.mutation.JdbcValueBindings; import org.hibernate.engine.jdbc.mutation.OperationResultChecker; @@ -25,6 +21,7 @@ import org.hibernate.persister.entity.mutation.EntityTableMapping; import org.hibernate.reactive.adaptor.impl.PrepareStatementDetailsAdaptor; import org.hibernate.reactive.adaptor.impl.PreparedStatementAdaptor; +import org.hibernate.reactive.engine.jdbc.ResultsCheckerUtil; import org.hibernate.reactive.engine.jdbc.env.internal.ReactiveMutationExecutor; import org.hibernate.reactive.generator.values.ReactiveGeneratedValuesMutationDelegate; import org.hibernate.reactive.logging.impl.Log; @@ -37,9 +34,16 @@ import org.hibernate.sql.model.TableMapping; import org.hibernate.sql.model.ValuesAnalysis; +import java.lang.invoke.MethodHandles; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletionStage; + import static org.hibernate.engine.jdbc.mutation.internal.ModelMutationHelper.checkResults; import static org.hibernate.reactive.logging.impl.LoggerFactory.make; import static org.hibernate.reactive.util.impl.CompletionStages.failedFuture; +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; import static org.hibernate.sql.model.ModelMutationLogging.MODEL_MUTATION_LOGGER; @@ -73,10 +77,64 @@ private ReactiveConnection connection(SharedSessionContractImplementor session) @Override public CompletionStage performReactiveBatchedOperations( ValuesAnalysis valuesAnalysis, - TableInclusionChecker inclusionChecker, OperationResultChecker resultChecker, + TableInclusionChecker inclusionChecker, + OperationResultChecker resultChecker, SharedSessionContractImplementor session) { - return ReactiveMutationExecutor.super - .performReactiveBatchedOperations( valuesAnalysis, inclusionChecker, resultChecker, session); + final PreparedStatementGroup batchedMutationOperationGroup = getBatchedPreparedStatementGroup(); + if ( batchedMutationOperationGroup != null ) { + final List preparedStatementDetailsList = new ArrayList<>( + batchedMutationOperationGroup.getNumberOfStatements() ); + batchedMutationOperationGroup.forEachStatement( (tableName, statementDetails) -> preparedStatementDetailsList + .add( statementDetails ) ); + return loop( preparedStatementDetailsList, statementDetails -> { + if ( statementDetails == null ) { + return voidFuture(); + } + final JdbcValueBindings valueBindings = getJdbcValueBindings(); + final TableMapping tableDetails = statementDetails.getMutatingTableDetails(); + if ( inclusionChecker != null && !inclusionChecker.include( tableDetails ) ) { + if ( MODEL_MUTATION_LOGGER.isTraceEnabled() ) { + MODEL_MUTATION_LOGGER.tracef( + "Skipping execution of secondary insert : %s", + tableDetails.getTableName() + ); + } + return voidFuture(); + } + + // If we get here the statement is needed - make sure it is resolved + final Object[] paramValues = PreparedStatementAdaptor.bind( statement -> { + PreparedStatementDetails details = new PrepareStatementDetailsAdaptor( + statementDetails, + statement, + session.getJdbcServices() + ); + valueBindings.beforeStatement( details ); + } ); + + final ReactiveConnection reactiveConnection = ( (ReactiveConnectionSupplier) session ).getReactiveConnection(); + final String sql = statementDetails.getSqlString(); + return reactiveConnection.update( + sql, + paramValues, + true, + (rowCount, batchPosition, query) -> ResultsCheckerUtil.checkResults( + session, + statementDetails, + resultChecker, + rowCount, + batchPosition + ) + ).whenComplete( (o, throwable) -> { //TODO: is this part really needed? + if ( statementDetails.getStatement() != null ) { + statementDetails.releaseStatement( session ); + } + valueBindings.afterStatement( tableDetails ); + } ); + } + ); + } + return voidFuture(); } @Override @@ -159,6 +217,22 @@ public CompletionStage performReactiveNonBatchedOperations( } } + public CompletionStage performReactiveSelfExecutingOperations( + ValuesAnalysis valuesAnalysis, + TableInclusionChecker inclusionChecker, + SharedSessionContractImplementor session) { + if ( getSelfExecutingMutations() == null || getSelfExecutingMutations().isEmpty() ) { + return voidFuture(); + } + + return loop( getSelfExecutingMutations(), operation -> { + if ( inclusionChecker.include( operation.getTableDetails() ) ) { + operation.performMutation( getJdbcValueBindings(), valuesAnalysis, session ); + } + return voidFuture(); + }); + } + private class OperationsForEach { private final Object id; @@ -210,6 +284,7 @@ public CompletionStage buildLoop() { return loop; } } + @Override public CompletionStage performReactiveNonBatchedMutation( PreparedStatementDetails statementDetails, From 64aa5dab25da0b74cff7351ad5276a4c25785ba4 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Tue, 27 Feb 2024 12:59:51 +0100 Subject: [PATCH 7/7] [#1867] Test case --- .../reactive/JoinedInheritanceBatchTest.java | 130 ++++++++ .../ManyToManyWithCompositeIdTest.java | 293 ++++++++++++++++++ 2 files changed, 423 insertions(+) create mode 100644 hibernate-reactive-core/src/test/java/org/hibernate/reactive/JoinedInheritanceBatchTest.java create mode 100644 hibernate-reactive-core/src/test/java/org/hibernate/reactive/ManyToManyWithCompositeIdTest.java diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/JoinedInheritanceBatchTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/JoinedInheritanceBatchTest.java new file mode 100644 index 000000000..cf505127c --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/JoinedInheritanceBatchTest.java @@ -0,0 +1,130 @@ +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.reactive; + + +import org.junit.jupiter.api.Test; + +import io.vertx.junit5.Timeout; +import io.vertx.junit5.VertxTestContext; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.SequenceGenerator; +import jakarta.persistence.Table; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CompletionStage; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; + +@Timeout(value = 10, timeUnit = MINUTES) +public class JoinedInheritanceBatchTest extends BaseReactiveTest { + + @Override + protected Collection> annotatedEntities() { + return List.of( ClientA.class, Client.class ); + } + + @Override + protected CompletionStage cleanDb() { + return voidFuture(); + } + + @Test + public void test(VertxTestContext context) { + final ClientA client1 = new ClientA("Client 1", "email@c1", "123456"); + + test( context, getMutinySessionFactory().withTransaction( session -> { + session.setBatchSize( 5 ); + return session.persist( client1 ); + } ) + .chain( () -> getMutinySessionFactory().withTransaction( session -> session + .createQuery( "select c from Client c" ) + .getResultList() + .invoke( persistedClients -> assertThat( persistedClients ) + .as( "Clients has not bee persisted" ) + .isNotEmpty() ) ) ) + ); + } + + @Entity(name = "Client") + @Table(name = "`Client`") + @Inheritance(strategy = InheritanceType.JOINED) + public static class Client { + + @Id + @SequenceGenerator(name = "seq", sequenceName = "id_seq", allocationSize = 1) + @GeneratedValue(generator = "seq", strategy = GenerationType.SEQUENCE) + private Long id; + + private String name; + + private String email; + + private String phone; + + public Client() { + } + + public Client(String name, String email, String phone) { + this.name = name; + this.email = email; + this.phone = phone; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + } + + @Entity + @Table(name = "`ClientA`") + public static class ClientA extends Client { + + public ClientA() { + } + + public ClientA(String name, String email, String phone) { + super( name, email, phone ); + } + } + +} diff --git a/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ManyToManyWithCompositeIdTest.java b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ManyToManyWithCompositeIdTest.java new file mode 100644 index 000000000..5e627d260 --- /dev/null +++ b/hibernate-reactive-core/src/test/java/org/hibernate/reactive/ManyToManyWithCompositeIdTest.java @@ -0,0 +1,293 @@ +/* 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 java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import io.vertx.junit5.VertxTestContext; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.IdClass; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.SequenceGenerator; +import jakarta.persistence.Table; +import java.util.concurrent.CompletionStage; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.hibernate.reactive.util.impl.CompletionStages.voidFuture; + +@Timeout(value = 10, timeUnit = MINUTES) +public class ManyToManyWithCompositeIdTest extends BaseReactiveTest { + + @Override + protected Collection> annotatedEntities() { + return List.of( CarsClients.class, ClientA.class, Client.class, Car.class ); + } + + @Override + protected CompletionStage cleanDb() { + return voidFuture(); + } + + @Test + public void test(VertxTestContext context) { + List clients = new ArrayList<>(); + for ( int i = 0; i < 5; i++ ) { + ClientA client = new ClientA(); + client.setName( "name" + i ); + client.setEmail( "email" + i ); + client.setPhone( "phone" + i ); + clients.add( client ); + } + + List cars = new ArrayList<>(); + for ( int i = 0; i < 2; i++ ) { + Car car = new Car(); + car.setBrand( "brand" + i ); + car.setModel( "model" + i ); + cars.add( car ); + } + + test( context, getMutinySessionFactory() + .withSession( session -> { + session.setBatchSize( 5 ); + return session.persistAll( cars.toArray() ) + .chain( () -> session + .persistAll( clients.toArray() ) + .chain( session::flush ) ) + .chain( () -> { + List carsClientsList = new ArrayList<>(); + for ( Client client : clients ) { + for ( Car car : cars ) { + CarsClients carsClients = new CarsClients( "location" ); + carsClientsList.add( carsClients ); + car.addClient( carsClients ); + client.addCar( carsClients ); + } + } + return session + .persistAll( carsClientsList.toArray() ) + .chain( session::flush ); + } ); + } ) + ); + } + + @Entity(name = "Car") + @Table(name = "Car") + public static class Car { + + @Id + @SequenceGenerator(name = "seq_car", sequenceName = "id_seq_car", allocationSize = 1) + @GeneratedValue(generator = "seq_car", strategy = GenerationType.SEQUENCE) + private Long id; + + public String brand; + + + private String model; + + @OneToMany(mappedBy = "car") + private Set clients = new HashSet<>(); + + public Car() { + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getBrand() { + return brand; + } + + public void setBrand(String brand) { + this.brand = brand; + } + + public String getModel() { + return model; + } + + public void setModel(String model) { + this.model = model; + } + + public Set getClients() { + return clients; + } + + public void setClients(Set clients) { + this.clients = clients; + } + + public void addClient(CarsClients carsClients) { + carsClients.setCar( this ); + clients.add( carsClients ); + } + } + + @Entity + @Table(name = "`Client`") + @Inheritance(strategy = InheritanceType.JOINED) + public static class Client { + + @Id + @SequenceGenerator(name = "seq", sequenceName = "id_seq", allocationSize = 1) + @GeneratedValue(generator = "seq", strategy = GenerationType.SEQUENCE) + private Long id; + + private String name; + + private String email; + + private String phone; + + @OneToMany(mappedBy = "client") + private Set cars = new HashSet<>(); + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public Set getCars() { + return cars; + } + + public void setCars(Set cars) { + this.cars = cars; + } + + public void addCar(CarsClients carsClients) { + carsClients.setClient( this ); + cars.add( carsClients ); + } + } + + @Entity + @Table(name = "`ClientA`") + public static class ClientA extends Client { + + public ClientA() { + } + } + + @Entity + @IdClass(CarsClientsId.class) + @Table(name = "cars_clients") + public static class CarsClients { + + @Id + @ManyToOne + private Car car; + + @Id + @ManyToOne + private Client client; + + private String location; + + public CarsClients() { + } + + public CarsClients(String location) { + this.location = location; + } + + public Car getCar() { + return car; + } + + public void setCar(Car car) { + this.car = car; + } + + public Client getClient() { + return client; + } + + public void setClient(Client client) { + this.client = client; + } + + public String getLocation() { + return location; + } + + public void setLocation(String location) { + this.location = location; + } + } + + public static class CarsClientsId { + private Car car; + + private Client client; + + public CarsClientsId() { + } + + public Car getCar() { + return car; + } + + public void setCar(Car car) { + this.car = car; + } + + public Client getClient() { + return client; + } + + public void setClient(Client client) { + this.client = client; + } + } +}