Skip to content

Fix support for @EmbeddedId #1988

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 4 commits into from
Sep 16, 2024
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
@@ -0,0 +1,107 @@
/* Hibernate, Relational Persistence for Idiomatic Java
*
* SPDX-License-Identifier: Apache-2.0
* Copyright: Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.reactive.metamodel.mapping.internal;

import java.util.function.BiConsumer;

import org.hibernate.engine.FetchTiming;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.metamodel.internal.AbstractCompositeIdentifierMapping;
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.internal.EmbeddedIdentifierMappingImpl;
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;

public class ReactiveEmbeddedIdentifierMappingImpl extends AbstractCompositeIdentifierMapping {

private final EmbeddedIdentifierMappingImpl delegate;

public ReactiveEmbeddedIdentifierMappingImpl(EmbeddedIdentifierMappingImpl delegate) {
super( delegate );
this.delegate = delegate;
}

@Override
public Fetch generateFetch(
FetchParent fetchParent,
NavigablePath fetchablePath,
FetchTiming fetchTiming,
boolean selected,
String resultVariable,
DomainResultCreationState creationState) {
return new ReactiveEmbeddableFetchImpl(
fetchablePath,
this,
fetchParent,
fetchTiming,
selected,
creationState
);
}

@Override
public EmbeddableMappingType getPartMappingType() {
return delegate.getPartMappingType();
}

@Override
public void applySqlSelections(
NavigablePath navigablePath,
TableGroup tableGroup,
DomainResultCreationState creationState) {
delegate.applySqlSelections( navigablePath, tableGroup, creationState );
}

@Override
public void applySqlSelections(
NavigablePath navigablePath,
TableGroup tableGroup,
DomainResultCreationState creationState,
BiConsumer<SqlSelection, JdbcMapping> selectionConsumer) {
delegate.applySqlSelections( navigablePath, tableGroup, creationState, selectionConsumer );
}

@Override
public EmbeddableMappingType getMappedIdEmbeddableTypeDescriptor() {
return delegate.getMappedIdEmbeddableTypeDescriptor();
}

@Override
public Nature getNature() {
return delegate.getNature();
}

@Override
public String getAttributeName() {
return delegate.getAttributeName();
}

@Override
public Object getIdentifier(Object entity) {
return delegate.getIdentifier( entity );
}

@Override
public void setIdentifier(Object entity, Object id, SharedSessionContractImplementor session) {
delegate.setIdentifier( entity, id, session );
}

@Override
public String getSqlAliasStem() {
return "";
}

@Override
public String getFetchableName() {
return delegate.getFetchableName();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import org.hibernate.metamodel.mapping.EntityValuedModelPart;
import org.hibernate.metamodel.mapping.ManagedMappingType;
import org.hibernate.metamodel.mapping.SingularAttributeMapping;
import org.hibernate.metamodel.mapping.internal.EmbeddedIdentifierMappingImpl;
import org.hibernate.metamodel.mapping.internal.GeneratedValuesProcessor;
import org.hibernate.metamodel.mapping.internal.MappingModelCreationHelper;
import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess;
Expand All @@ -56,6 +57,7 @@
import org.hibernate.reactive.loader.ast.spi.ReactiveSingleUniqueKeyEntityLoader;
import org.hibernate.reactive.logging.impl.Log;
import org.hibernate.reactive.logging.impl.LoggerFactory;
import org.hibernate.reactive.metamodel.mapping.internal.ReactiveEmbeddedIdentifierMappingImpl;
import org.hibernate.reactive.metamodel.mapping.internal.ReactivePluralAttributeMapping;
import org.hibernate.reactive.metamodel.mapping.internal.ReactiveToOneAttributeMapping;
import org.hibernate.reactive.sql.results.graph.embeddable.internal.ReactiveNonAggregatedIdentifierMappingFetch;
Expand Down Expand Up @@ -324,6 +326,9 @@ public EntityIdentifierMapping convertEntityIdentifierMapping(EntityIdentifierMa
if ( entityIdentifierMapping instanceof NonAggregatedIdentifierMappingImpl ) {
return new ReactiveNonAggregatedIdentifierMappingImpl( (NonAggregatedIdentifierMappingImpl) entityIdentifierMapping );
}
if ( entityIdentifierMapping instanceof EmbeddedIdentifierMappingImpl ) {
return new ReactiveEmbeddedIdentifierMappingImpl( (EmbeddedIdentifierMappingImpl) entityIdentifierMapping );
}
return entityIdentifierMapping;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,15 @@ public CompletionStage<Void> forEachReactiveSubInitializer(
}
}

@Override
public void resolveInstance(EmbeddableInitializerData data) {
// We need to clean up the instance, otherwise the .find with multiple id is not going to work correctly.
// It will only return the first element of the list. See EmbeddedIdTest#testFindMultipleIds.
// ORM doesn't have this issue because they don't have a find with multiple ids.
data.setInstance( null );
super.resolveInstance( data );
}

@Override
public Object getResolvedInstance(EmbeddableInitializerData data) {
return super.getResolvedInstance( data );
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
/* 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.Objects;
import java.util.Set;

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.Column;
import jakarta.persistence.Embeddable;
import jakarta.persistence.EmbeddedId;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;

import static java.util.concurrent.TimeUnit.MINUTES;
import static org.assertj.core.api.Assertions.assertThat;

@Timeout(value = 10, timeUnit = MINUTES)
public class EmbeddedIdTest extends BaseReactiveTest {

@Override
protected Collection<Class<?>> annotatedEntities() {
return Set.of( Delivery.class );
}

LocationId verbania = new LocationId( "Italy", "Verbania" );
Delivery pizza = new Delivery( verbania, "Pizza Margherita" );

LocationId hallein = new LocationId( "Austria", "Hallein" );
Delivery schnitzel = new Delivery( hallein, "Wiener Schnitzel" );

@BeforeEach
public void populateDb(VertxTestContext context) {
test( context, getMutinySessionFactory().withTransaction( s -> s.persistAll( pizza, schnitzel ) ) );
}

@Test
public void testFindSingleId(VertxTestContext context) {
test( context, getMutinySessionFactory().withTransaction( s -> s.find( Delivery.class, verbania ) )
.invoke( result -> assertThat( result ).isEqualTo( pizza ) )
);
}

@Test
public void testFindMultipleIds(VertxTestContext context) {
test( context, getMutinySessionFactory().withTransaction( s -> s.find( Delivery.class, verbania, hallein ) )
.invoke( result -> assertThat( result ).containsExactlyInAnyOrder( pizza, schnitzel ) )
);
}

@Entity(name = "Delivery")
@Table(name = "Delivery")
public static class Delivery {

@EmbeddedId
private LocationId locationId;

@Column(name = "field")
private String field;

public Delivery() {
}

public Delivery(LocationId locationId, String field) {
this.locationId = locationId;
this.field = field;
}

public LocationId getLocationId() {
return locationId;
}

public void setLocationId(LocationId locationId) {
this.locationId = locationId;
}

public String getField() {
return field;
}

public void setField(String field) {
this.field = field;
}

@Override
public String toString() {
return locationId + ":" + field;
}

@Override
public boolean equals(Object o) {
if ( this == o ) {
return true;
}
if ( o == null || getClass() != o.getClass() ) {
return false;
}
Delivery table = (Delivery) o;
return Objects.equals( locationId, table.locationId ) &&
Objects.equals( field, table.field );
}

@Override
public int hashCode() {
return Objects.hash( locationId, field );
}
}


@Embeddable
public static class LocationId {

@Column(name = "sp_country")
private String country;

@Column(name = "sp_city")
private String city;

public LocationId(String country, String city) {
this.country = country;
this.city = city;
}

public LocationId() {
}

public String getCountry() {
return country;
}

public String getCity() {
return city;
}

public void setCountry(String country) {
this.country = country;
}

public void setCity(String city) {
this.city = city;
}

@Override
public String toString() {
return "[" + country + "-" + city + "]";
}

@Override
public boolean equals(Object o) {
if ( this == o ) {
return true;
}
if ( o == null || getClass() != o.getClass() ) {
return false;
}
LocationId tableId = (LocationId) o;
return Objects.equals( country, tableId.country ) &&
Objects.equals( city, tableId.city );
}

@Override
public int hashCode() {
return Objects.hash( country, city );
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,20 @@ private static String nameFromResult(List<Object> rowSet) {
}
}

@Test
public void reactiveFindMultipleIds(VertxTestContext context) {
final GuineaPig rump = new GuineaPig( 55, "Rumpelstiltskin" );
final GuineaPig emma = new GuineaPig( 77, "Emma" );
test( context, populateDB()
.chain( () -> getMutinySessionFactory().withTransaction( s -> s.persistAll( emma, rump ) ) )
.chain( () -> getMutinySessionFactory().withTransaction( s -> s.find( GuineaPig.class, emma.getId(), rump.getId() ) )
)
.invoke( pigs -> {
org.assertj.core.api.Assertions.assertThat( pigs ).containsExactlyInAnyOrder( emma, rump );
} )
);
}

@Test
public void sessionClear(VertxTestContext context) {
final GuineaPig guineaPig = new GuineaPig( 81, "Perry" );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,21 @@ public void reactiveFind(VertxTestContext context) {
);
}

@Test
public void reactiveFindMultipleIds(VertxTestContext context) {
final GuineaPig rump = new GuineaPig( 55, "Rumpelstiltskin" );
final GuineaPig emma = new GuineaPig( 77, "Emma" );
test( context, populateDB()
.thenCompose( v -> getSessionFactory().withTransaction( s -> s.persist( emma, rump ) ) )
.thenCompose( v -> getSessionFactory().withTransaction( s -> s
.find( GuineaPig.class, emma.getId(), rump.getId() ) )
)
.thenAccept( pigs -> {
org.assertj.core.api.Assertions.assertThat( pigs ).containsExactlyInAnyOrder( emma, rump );
} )
);
}

@Test
public void sessionClear(VertxTestContext context) {
final GuineaPig guineaPig = new GuineaPig( 81, "Perry" );
Expand Down
Loading