diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java index 786e6dc7f8..5432f18e16 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java @@ -2340,8 +2340,7 @@ public abstract class CollectionBinder { } else { //force in case of attribute override - final boolean attributeOverride = property.isAnnotationPresent( AttributeOverride.class ) - || property.isAnnotationPresent( AttributeOverrides.class ); + final boolean attributeOverride = mappingDefinedAttributeOverrideOnElement(property); // todo : force in the case of Convert annotation(s) with embedded paths (beyond key/value prefixes)? return isEmbedded || attributeOverride ? EMBEDDABLE @@ -2349,6 +2348,11 @@ public abstract class CollectionBinder { } } + protected boolean mappingDefinedAttributeOverrideOnElement(XProperty property) { + return property.isAnnotationPresent( AttributeOverride.class ) + || property.isAnnotationPresent( AttributeOverrides.class ); + } + static AnnotatedColumns createElementColumnsIfNecessary( Collection collection, AnnotatedColumns elementColumns, diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/MapBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/MapBinder.java index a5b5665fdf..745a444b79 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/MapBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/MapBinder.java @@ -107,8 +107,27 @@ public class MapBinder extends CollectionBinder { }; } - private void makeOneToManyMapKeyColumnNullableIfNotInProperty( - final XProperty property) { + @Override + protected boolean mappingDefinedAttributeOverrideOnElement(XProperty property) { + if ( property.isAnnotationPresent( AttributeOverride.class ) ) { + return namedMapValue( property.getAnnotation( AttributeOverride.class ) ); + } + if ( property.isAnnotationPresent( AttributeOverrides.class ) ) { + final AttributeOverrides annotations = property.getAnnotation( AttributeOverrides.class ); + for ( AttributeOverride attributeOverride : annotations.value() ) { + if ( namedMapValue( attributeOverride ) ) { + return true; + } + } + } + return false; + } + + private boolean namedMapValue(AttributeOverride annotation) { + return annotation.name().startsWith( "value." ); + } + + private void makeOneToManyMapKeyColumnNullableIfNotInProperty(XProperty property) { final Map map = (Map) this.collection; if ( map.isOneToMany() && property.isAnnotationPresent( MapKeyColumn.class ) ) { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/attributeoverride/MapAttributeOverrideTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/attributeoverride/MapAttributeOverrideTest.java new file mode 100644 index 0000000000..af9db96cca --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/attributeoverride/MapAttributeOverrideTest.java @@ -0,0 +1,45 @@ +package org.hibernate.orm.test.annotations.attributeoverride; + +import java.time.LocalTime; +import java.util.Map; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.metamodel.EntityType; +import jakarta.persistence.metamodel.MapAttribute; + +import static jakarta.persistence.metamodel.Type.PersistenceType.BASIC; +import static jakarta.persistence.metamodel.Type.PersistenceType.EMBEDDABLE; +import static org.assertj.core.api.Assertions.assertThat; + +@DomainModel( + annotatedClasses = { + Schedule.class, + Route.class + } +) +@SessionFactory +class MapAttributeOverrideTest { + + @Test + @JiraKey("HHH-18516") + void testMapOfEmbeddableKeysWithAttributeOverridesAndBasicValues(SessionFactoryScope sessionFactoryScope) { + sessionFactoryScope.inTransaction(session -> { + EntityType scheduleType = session.getMetamodel().entity(Schedule.class); + MapAttribute departuresMapAttribute = scheduleType.getMap("departures"); + // Presence of @AttributeOverride-s for the key should only affect the type of the key, but not the type of the value + assertThat(departuresMapAttribute.getKeyType().getPersistenceType()).isEqualTo(EMBEDDABLE); + assertThat(departuresMapAttribute.getElementType().getPersistenceType()).isEqualTo(BASIC); + + session.persist(new Schedule(Map.of( + new Route("Hamburg", "Vienna"), LocalTime.NOON, + new Route("Warsaw", "Barcelona"), LocalTime.MIDNIGHT + ))); + assertThat(session.createQuery("FROM Schedule s", Schedule.class).getResultCount()).isOne(); + }); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/attributeoverride/Route.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/attributeoverride/Route.java new file mode 100644 index 0000000000..1e3951ea1c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/attributeoverride/Route.java @@ -0,0 +1,18 @@ +package org.hibernate.orm.test.annotations.attributeoverride; + +import jakarta.persistence.Embeddable; + +@Embeddable +public class Route { + + private String origin; + private String destination; + + public Route() { + } + + public Route(String origin, String destination) { + this.origin = origin; + this.destination = destination; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/attributeoverride/Schedule.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/attributeoverride/Schedule.java new file mode 100644 index 0000000000..1e5f035621 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/attributeoverride/Schedule.java @@ -0,0 +1,32 @@ +package org.hibernate.orm.test.annotations.attributeoverride; + +import java.time.LocalTime; +import java.util.Map; + +import jakarta.persistence.AttributeOverride; +import jakarta.persistence.Column; +import jakarta.persistence.ElementCollection; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; + +@Entity +public class Schedule { + + @Id + @GeneratedValue + private Long id; + + @ElementCollection + @Column(name = "time") + @AttributeOverride(name = "key.origin", column = @Column(name = "orig")) + @AttributeOverride(name = "key.destination", column = @Column(name = "dest")) + private Map departures; + + public Schedule() { + } + + public Schedule(Map departures) { + this.departures = departures; + } +}