Skip to content

[2.4] ClassCastException when with @EmebedddedId and @OneToOne relationship #2289

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.internal.EmbeddedIdentifierMappingImpl;
import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
import org.hibernate.reactive.sql.results.graph.embeddable.internal.ReactiveEmbeddableFetchImpl;
import org.hibernate.spi.NavigablePath;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.sql.results.graph.Fetch;
import org.hibernate.sql.results.graph.FetchParent;
import org.hibernate.sql.results.graph.Fetchable;

public class ReactiveEmbeddedIdentifierMappingImpl extends AbstractCompositeIdentifierMapping {

Expand Down Expand Up @@ -104,4 +106,13 @@ public String getSqlAliasStem() {
public String getFetchableName() {
return delegate.getFetchableName();
}

@Override
public Fetchable getFetchable(int position) {
Fetchable fetchable = delegate.getFetchable( position );
if ( fetchable instanceof ToOneAttributeMapping ) {
return new ReactiveToOneAttributeMapping( (ToOneAttributeMapping) fetchable );
}
return fetchable;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/* Hibernate, Relational Persistence for Idiomatic Java
*
* SPDX-License-Identifier: Apache-2.0
* Copyright: Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.reactive.sql.results.graph.embeddable.internal;

import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState;
import org.hibernate.reactive.sql.results.graph.ReactiveDomainResultsAssembler;
import org.hibernate.reactive.sql.results.graph.ReactiveInitializer;
import org.hibernate.sql.results.graph.Initializer;
import org.hibernate.sql.results.graph.InitializerData;
import org.hibernate.sql.results.graph.embeddable.EmbeddableInitializer;
import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableAssembler;
import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions;

import java.util.concurrent.CompletionStage;

import static org.hibernate.reactive.util.impl.CompletionStages.completedFuture;

/**
* @see org.hibernate.sql.results.graph.embeddable.internal.EmbeddableAssembler
*/
public class ReactiveEmbeddableAssembler extends EmbeddableAssembler implements ReactiveDomainResultsAssembler {

public ReactiveEmbeddableAssembler(EmbeddableInitializer<?> initializer) {
super( initializer );
}

@Override
public CompletionStage<Object> reactiveAssemble(ReactiveRowProcessingState rowProcessingState, JdbcValuesSourceProcessingOptions options) {
final ReactiveInitializer<InitializerData> reactiveInitializer = (ReactiveInitializer<InitializerData>) getInitializer();
final InitializerData data = reactiveInitializer.getData( rowProcessingState );
final Initializer.State state = data.getState();
if ( state == Initializer.State.KEY_RESOLVED ) {
return reactiveInitializer
.reactiveResolveInstance( data )
.thenApply( v -> reactiveInitializer.getResolvedInstance( data ) );
}
return completedFuture( reactiveInitializer.getResolvedInstance( data ) );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,20 @@
package org.hibernate.reactive.sql.results.graph.embeddable.internal;

import org.hibernate.engine.FetchTiming;
import org.hibernate.reactive.sql.results.graph.entity.internal.ReactiveEntityFetchSelectImpl;
import org.hibernate.spi.NavigablePath;
import org.hibernate.sql.results.graph.AssemblerCreationState;
import org.hibernate.sql.results.graph.DomainResultAssembler;
import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.sql.results.graph.Fetch;
import org.hibernate.sql.results.graph.FetchParent;
import org.hibernate.sql.results.graph.Fetchable;
import org.hibernate.sql.results.graph.Initializer;
import org.hibernate.sql.results.graph.InitializerParent;
import org.hibernate.sql.results.graph.embeddable.EmbeddableInitializer;
import org.hibernate.sql.results.graph.embeddable.EmbeddableValuedFetchable;
import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableFetchImpl;
import org.hibernate.sql.results.graph.entity.internal.EntityFetchSelectImpl;

public class ReactiveEmbeddableFetchImpl extends EmbeddableFetchImpl {

Expand All @@ -37,4 +43,19 @@ public EmbeddableInitializer<?> createInitializer(
AssemblerCreationState creationState) {
return new ReactiveEmbeddableInitializerImpl( this, getDiscriminatorFetch(), parent, creationState, true );
}

@Override
public DomainResultAssembler<?> createAssembler(InitializerParent<?> parent, AssemblerCreationState creationState) {
Initializer<?> initializer = creationState.resolveInitializer( this, parent, this );
EmbeddableInitializer<?> embeddableInitializer = initializer.asEmbeddableInitializer();
return new ReactiveEmbeddableAssembler( embeddableInitializer );
}

@Override
public Fetch findFetch(Fetchable fetchable) {
Fetch fetch = super.findFetch( fetchable );
return fetch instanceof EntityFetchSelectImpl
? new ReactiveEntityFetchSelectImpl( (EntityFetchSelectImpl) fetch )
: fetch;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,19 @@
*/
package org.hibernate.reactive.sql.results.graph.embeddable.internal;


import java.util.concurrent.CompletionStage;
import java.util.function.BiFunction;

import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
import org.hibernate.metamodel.mapping.VirtualModelPart;
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
import org.hibernate.reactive.sql.exec.spi.ReactiveRowProcessingState;
import org.hibernate.reactive.sql.results.graph.ReactiveDomainResultsAssembler;
import org.hibernate.reactive.sql.results.graph.ReactiveInitializer;
import org.hibernate.sql.results.graph.AssemblerCreationState;
import org.hibernate.sql.results.graph.DomainResultAssembler;
import org.hibernate.sql.results.graph.Initializer;
import org.hibernate.sql.results.graph.InitializerData;
import org.hibernate.sql.results.graph.InitializerParent;
Expand All @@ -19,12 +26,19 @@
import org.hibernate.sql.results.graph.embeddable.internal.EmbeddableInitializerImpl;
import org.hibernate.sql.results.jdbc.spi.RowProcessingState;

import static org.hibernate.reactive.util.impl.CompletionStages.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;
import static org.hibernate.reactive.util.impl.CompletionStages.whileLoop;
import static org.hibernate.sql.results.graph.embeddable.EmbeddableLoadingLogger.EMBEDDED_LOAD_LOGGER;
import static org.hibernate.sql.results.graph.entity.internal.BatchEntityInsideEmbeddableSelectFetchInitializer.BATCH_PROPERTY;

public class ReactiveEmbeddableInitializerImpl extends EmbeddableInitializerImpl
implements ReactiveInitializer<EmbeddableInitializerImpl.EmbeddableInitializerData> {

private final SessionFactoryImplementor sessionFactory;

private static class ReactiveEmbeddableInitializerData extends EmbeddableInitializerData {

public ReactiveEmbeddableInitializerData(
Expand All @@ -33,6 +47,10 @@ public ReactiveEmbeddableInitializerData(
super( initializer, rowProcessingState );
}

public Object[] getRowState(){
return rowState;
}

@Override
public void setState(State state) {
super.setState( state );
Expand All @@ -55,6 +73,7 @@ public ReactiveEmbeddableInitializerImpl(
AssemblerCreationState creationState,
boolean isResultInitializer) {
super( resultDescriptor, discriminatorFetch, parent, creationState, isResultInitializer );
sessionFactory = creationState.getSqlAstCreationContext().getSessionFactory();
}

@Override
Expand All @@ -64,10 +83,129 @@ protected InitializerData createInitializerData(RowProcessingState rowProcessing

@Override
public CompletionStage<Void> reactiveResolveInstance(EmbeddableInitializerData data) {
super.resolveInstance( data );
if ( data.getState() != State.KEY_RESOLVED ) {
return voidFuture();
}

data.setState( State.RESOLVED );
return extractRowState( (ReactiveEmbeddableInitializerData) data )
.thenAccept( unused -> prepareCompositeInstance( (ReactiveEmbeddableInitializerData) data ) );
}

private CompletionStage<Void> extractRowState(ReactiveEmbeddableInitializerData data) {
final DomainResultAssembler<?>[] subAssemblers = assemblers[data.getSubclassId()];
final RowProcessingState rowProcessingState = data.getRowProcessingState();
final Object[] rowState = data.getRowState();
final boolean[] stateAllNull = {true};
final int[] index = {0};
final boolean[] forceExit = { false };
return whileLoop(
() -> index[0] < subAssemblers.length && !forceExit[0],
() -> {
final int i = index[0]++;
final DomainResultAssembler<?> assembler = subAssemblers[i];
if ( assembler instanceof ReactiveDomainResultsAssembler<?> ) {
return ( (ReactiveDomainResultsAssembler<?>) assembler )
.reactiveAssemble( (ReactiveRowProcessingState) rowProcessingState )
.thenAccept( contributorValue -> setContributorValue(
contributorValue,
i,
rowState,
stateAllNull,
forceExit
) );
}
else {
setContributorValue(
assembler == null ? null : assembler.assemble( rowProcessingState ),
i,
rowState,
stateAllNull,
forceExit
);
return voidFuture();
}
})
.whenComplete(
(unused, throwable) -> {
if ( stateAllNull[0] ) {
data.setState( State.MISSING );
}
}
);
}

private void setContributorValue(
Object contributorValue,
int index,
Object[] rowState,
boolean[] stateAllNull,
boolean[] forceExit) {
if ( contributorValue == BATCH_PROPERTY ) {
rowState[index] = null;
}
else {
rowState[index] = contributorValue;
}
if ( contributorValue != null ) {
stateAllNull[0] = false;
}
else if ( isPartOfKey() ) {
// If this is a foreign key and there is a null part, the whole thing has to be turned into null
stateAllNull[0] = true;
forceExit[0] = true;
}
}

private CompletionStage<Void> prepareCompositeInstance(ReactiveEmbeddableInitializerData data) {
// Virtual model parts use the owning entity as container which the fetch parent access provides.
// For an identifier or foreign key this is called during the resolveKey phase of the fetch parent,
// so we can't use the fetch parent access in that case.
final ReactiveInitializer<ReactiveEmbeddableInitializerData> parent = (ReactiveInitializer<ReactiveEmbeddableInitializerData>) getParent();
if ( parent != null && getInitializedPart() instanceof VirtualModelPart && !isPartOfKey() && data.getState() != State.MISSING ) {
final ReactiveEmbeddableInitializerData subData = parent.getData( data.getRowProcessingState() );
return parent
.reactiveResolveInstance( subData )
.thenCompose(
unused -> {
data.setInstance( parent.getResolvedInstance( subData ) );
if ( data.getState() == State.INITIALIZED ) {
return voidFuture();
}
return doCreateCompositeInstance( data )
.thenAccept( v -> EMBEDDED_LOAD_LOGGER.debugf(
"Created composite instance [%s]",
getNavigablePath()
) );
} );
}

return doCreateCompositeInstance( data )
.thenAccept( v -> EMBEDDED_LOAD_LOGGER.debugf( "Created composite instance [%s]", getNavigablePath() ) );

}

private CompletionStage<Void> doCreateCompositeInstance(ReactiveEmbeddableInitializerData data) {
if ( data.getInstance() == null ) {
return createCompositeInstance( data )
.thenAccept( data::setInstance );
}
return voidFuture();
}

private CompletionStage<Object> createCompositeInstance(ReactiveEmbeddableInitializerData data) {
if ( data.getState() == State.MISSING ) {
return nullFuture();
}

final EmbeddableInstantiator instantiator = data.getConcreteEmbeddableType() == null
? getInitializedPart().getEmbeddableTypeDescriptor().getRepresentationStrategy().getInstantiator()
: data.getConcreteEmbeddableType().getInstantiator();
final Object instance = instantiator.instantiate( data, sessionFactory );
data.setState( State.RESOLVED );
return completedFuture( instance );
}

@Override
public CompletionStage<Void> reactiveInitializeInstance(EmbeddableInitializerData data) {
super.initializeInstance( data );
Expand Down
Loading
Loading