Skip to content
Open
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 @@ -335,6 +335,7 @@ static void defineFetchingStrategy(
handleLazy( toOne, property );
handleFetch( toOne, property );
handleFetchProfileOverrides( toOne, property, propertyHolder, inferredData );
handleMapsId( toOne, property );
}

private static void handleLazy(ToOne toOne, MemberDetails property) {
Expand Down Expand Up @@ -373,6 +374,13 @@ private static void handleFetch(ToOne toOne, MemberDetails property) {
}
}

private static void handleMapsId(ToOne toOne, MemberDetails property) {
final MapsId mapsIdAnnotation = property.getDirectAnnotationUsage( MapsId.class );
if ( mapsIdAnnotation != null ) {
toOne.setHasMapsId( true );
}
}

private static void setHibernateFetchMode(ToOne toOne, MemberDetails property, org.hibernate.annotations.FetchMode fetchMode) {
switch ( fetchMode ) {
case JOIN:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ public void addGeneratedValuePlan(GenerationPlan plan) {
public Object generate(SharedSessionContractImplementor session, Object object) {
final Object context = generationContextLocator.locateGenerationContext( session, object );
final List<Object> generatedValues = generatedValues( session, object, context );
if ( generatedValues != null) {
if ( generatedValues != null ) {
final Object[] values = compositeType.getPropertyValues( context );
for ( int i = 0; i < generatedValues.size(); i++ ) {
values[generationPlans.get( i ).getPropertyIndex()] = generatedValues.get( i );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,13 @@
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.metamodel.mapping.DiscriminatorType;
import org.hibernate.metamodel.mapping.EmbeddableDiscriminatorConverter;
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
import org.hibernate.metamodel.mapping.internal.DiscriminatorTypeImpl;
import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
import org.hibernate.persister.entity.DiscriminatorHelper;
import org.hibernate.models.spi.ClassDetails;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.property.access.spi.Setter;
import org.hibernate.resource.beans.internal.FallbackBeanInstanceProducer;
import org.hibernate.type.ComponentType;
Expand Down Expand Up @@ -763,7 +766,28 @@ public StandardGenerationContextLocator(String entityName) {

@Override
public Object locateGenerationContext(SharedSessionContractImplementor session, Object incomingObject) {
return session.getEntityPersister( entityName, incomingObject ).getIdentifier( incomingObject, session );
final var persister = session.getEntityPersister( entityName, incomingObject );
final var context = persister.getIdentifier( incomingObject, session );
if ( context != null ) {
return context;
}

if ( persister.getIdentifierMapping() instanceof EmbeddableValuedModelPart embeddableId && containsMapsId( persister ) ) {
final var strategy = embeddableId.getEmbeddableTypeDescriptor().getRepresentationStrategy();
return strategy.getInstantiator().instantiate( null );
}
return null;
}

private static boolean containsMapsId(EntityPersister persister) {
final var attributeMappings = persister.getAttributeMappings();
for ( var i = 0; i < attributeMappings.size(); i++ ) {
final var attributeMapping = attributeMappings.get( i );
if ( attributeMapping instanceof ToOneAttributeMapping toOneMapping && toOneMapping.hasMapsId() ) {
return true;
}
}
return false;
}
}

Expand Down
9 changes: 9 additions & 0 deletions hibernate-core/src/main/java/org/hibernate/mapping/ToOne.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public abstract sealed class ToOne
private boolean unwrapProxy;
private boolean unwrapProxyImplicit;
private boolean referenceToPrimaryKey = true;
private boolean hasMapsId = false;

protected ToOne(MetadataBuildingContext buildingContext, Table table) {
super( buildingContext, table );
Expand Down Expand Up @@ -79,6 +80,14 @@ public void setReferencedEntityName(String referencedEntityName) {
null : referencedEntityName.intern();
}

public boolean hasMapsId() {
return hasMapsId;
}

public void setHasMapsId(boolean hasMapsId) {
this.hasMapsId = hasMapsId;
}

public String getPropertyName() {
return propertyName;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ public class Entity1 {

private final Cardinality cardinality;
private final boolean hasJoinTable;
private final boolean hasMapsId;
/*
Capture the other side's name of a possibly bidirectional association to allow resolving circular fetches.
It may be null if the referenced property is a non-entity.
Expand Down Expand Up @@ -183,6 +184,7 @@ protected ToOneAttributeMapping(ToOneAttributeMapping original) {
targetKeyPropertyName = original.targetKeyPropertyName;
cardinality = original.cardinality;
hasJoinTable = original.hasJoinTable;
hasMapsId = original.hasMapsId;
bidirectionalAttributePath = original.bidirectionalAttributePath;
declaringTableGroupProducer = original.declaringTableGroupProducer;
isKeyTableNullable = original.isKeyTableNullable;
Expand Down Expand Up @@ -250,6 +252,7 @@ public ToOneAttributeMapping(
);
sqlAliasStem = SqlAliasStemHelper.INSTANCE.generateStemFromAttributeName( name );
isNullable = bootValue.isNullable();
hasMapsId = bootValue.hasMapsId();
isLazy = navigableRole.getParent().getParent() == null
&& declaringEntityPersister.getBytecodeEnhancementMetadata()
.getLazyAttributesMetadata()
Expand Down Expand Up @@ -679,6 +682,7 @@ private ToOneAttributeMapping(
this.targetKeyPropertyNames = original.targetKeyPropertyNames;
this.cardinality = original.cardinality;
this.hasJoinTable = original.hasJoinTable;
this.hasMapsId = original.hasMapsId;
this.bidirectionalAttributePath = original.bidirectionalAttributePath;
this.declaringTableGroupProducer = declaringTableGroupProducer;
this.isInternalLoadNullable = original.isInternalLoadNullable;
Expand Down Expand Up @@ -2395,6 +2399,10 @@ public boolean hasNotFoundAction() {
return notFoundAction != null;
}

public boolean hasMapsId() {
return hasMapsId;
}

@Override
public boolean isUnwrapProxy() {
return unwrapProxy;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.orm.test.annotations.mapsid;

import jakarta.persistence.CascadeType;
import jakarta.persistence.EmbeddedId;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.MapsId;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne;
import org.hibernate.id.IdentifierGenerationException;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.Jira;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.Test;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

import static org.junit.jupiter.api.Assertions.assertThrows;

@SessionFactory
@DomainModel(
annotatedClasses = {
MapsEmbeddedIdNullTest.Level0.class,
MapsEmbeddedIdNullTest.Level1.class,
MapsEmbeddedIdNullTest.Level2.class,
MapsEmbeddedIdNullTest.Level1NoMapsId.class,
MapsEmbeddedIdNullTest.Level1OneToOne.class,
MapsEmbeddedIdNullTest.Level1NoMapsIdOneToOne.class,
})
@Jira("https://hibernate.atlassian.net/browse/HHH-19056")
public class MapsEmbeddedIdNullTest {

@Test
void test(SessionFactoryScope scope) {
scope.inTransaction( s -> {
Level0 level0 = new Level0();
Level2 level2 = new Level2();
Level1 level1 = new Level1( level0, level2 );
level0.level1s.add( level1 );
s.persist( level0 );
} );

scope.inTransaction( s -> {
Level0 level0 = new Level0();
Level2 level2 = new Level2();
Level1OneToOne level1 = new Level1OneToOne( level0, level2 );
s.persist( level1 );
} );

assertThrows( IdentifierGenerationException.class, () ->
scope.inTransaction( s -> {
Level0 level0 = new Level0();
Level2 level2 = new Level2();
Level1NoMapsId level1NoMapsId = new Level1NoMapsId();
level1NoMapsId.level0 = level0;
level1NoMapsId.level2 = level2;
s.persist( level1NoMapsId );
} )
);

assertThrows( IdentifierGenerationException.class, () ->
scope.inTransaction( s -> {
Level0 level0 = new Level0();
Level2 level2 = new Level2();
Level1NoMapsIdOneToOne level1 = new Level1NoMapsIdOneToOne();
level1.level0 = level0;
level1.level2 = level2;
s.persist( level1 );
} )
);
}

@Entity(name = "Level0")
public static class Level0 {
@Id
@GeneratedValue
private Integer id;
@OneToMany(mappedBy = "level0", cascade = CascadeType.ALL)
private List<Level1> level1s = new ArrayList<>();
}

@Entity(name = "Level1")
public static class Level1 {
@EmbeddedId
Level1PK id;
@MapsId("level0Id")
@ManyToOne
private Level0 level0;
@MapsId("level2Id")
@ManyToOne(cascade = CascadeType.ALL)
private Level2 level2;

public Level1() {
}

public Level1(Level0 level0, Level2 level2) {
super();
this.level0 = level0;
this.level2 = level2;
}
}

@Entity(name = "Level1OneToOne")
public static class Level1OneToOne {
@EmbeddedId
Level1PK id;
@OneToOne
@MapsId("level0Id")
private Level0 level0;
@OneToOne
@MapsId("level2Id")
private Level2 level2;

public Level1OneToOne() {
}

public Level1OneToOne(Level0 level0, Level2 level2) {
super();
this.level0 = level0;
this.level2 = level2;
}
}


@Entity(name = "Level1NoMapsId")
public static class Level1NoMapsId {
@EmbeddedId
Level1PK id;
@ManyToOne
private Level0 level0;
@ManyToOne(cascade = CascadeType.ALL)
private Level2 level2;
}

@Entity(name = "Level1NoMapsIdOneToOne")
public static class Level1NoMapsIdOneToOne {
@EmbeddedId
Level1PK id;
@ManyToOne
private Level0 level0;
@ManyToOne(cascade = CascadeType.ALL)
private Level2 level2;
}

@Entity(name = "Level2")
public static class Level2 {
@Id
@GeneratedValue
private Integer id;
}

public static class Level1PK {
private Integer level0Id;
private Integer level2Id;

@Override
public final boolean equals(Object o) {
if ( !(o instanceof Level1PK level1PK) ) {
return false;
}

return Objects.equals( level0Id, level1PK.level0Id )
&& Objects.equals( level2Id, level1PK.level2Id );
}

@Override
public int hashCode() {
int result = Objects.hashCode( level0Id );
result = 31 * result + Objects.hashCode( level2Id );
return result;
}
}
}