diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentPropertyImpl.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentPropertyImpl.java index 08fb038954..9f5efda42d 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentPropertyImpl.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentPropertyImpl.java @@ -109,7 +109,7 @@ public JpaPersistentPropertyImpl(JpaMetamodel metamodel, Property property, this.isIdProperty = Lazy.of(() -> ID_ANNOTATIONS.stream().anyMatch(it -> isAnnotationPresent(it)) // || metamodel.isSingleIdAttribute(getOwner().getType(), getName(), getType())); - this.isEntity = Lazy.of(() -> metamodel.isJpaManaged(getActualType())); + this.isEntity = Lazy.of(() -> metamodel.isMappedType(getActualType())); } @Override diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/util/JpaMetamodel.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/util/JpaMetamodel.java index 8331db055c..d417e2edd4 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/util/JpaMetamodel.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/util/JpaMetamodel.java @@ -16,15 +16,20 @@ package org.springframework.data.jpa.util; import java.util.Collection; +import java.util.EnumSet; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import jakarta.persistence.Embeddable; import jakarta.persistence.metamodel.EntityType; import jakarta.persistence.metamodel.ManagedType; import jakarta.persistence.metamodel.Metamodel; import jakarta.persistence.metamodel.SingularAttribute; +import jakarta.persistence.metamodel.Type.PersistenceType; +import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.data.util.Lazy; import org.springframework.data.util.StreamUtils; import org.springframework.util.Assert; @@ -39,10 +44,13 @@ public class JpaMetamodel { private static final Map CACHE = new ConcurrentHashMap<>(4); + private static final Set ENTITY_OR_MAPPED_SUPERCLASS = EnumSet.of(PersistenceType.ENTITY, + PersistenceType.MAPPED_SUPERCLASS); private final Metamodel metamodel; private Lazy>> managedTypes; + private Lazy>> jpaEmbeddables; /** * Creates a new {@link JpaMetamodel} for the given JPA {@link Metamodel}. @@ -54,10 +62,17 @@ private JpaMetamodel(Metamodel metamodel) { Assert.notNull(metamodel, "Metamodel must not be null!"); this.metamodel = metamodel; + this.managedTypes = Lazy.of(() -> metamodel.getManagedTypes().stream() // .map(ManagedType::getJavaType) // .filter(it -> it != null) // .collect(StreamUtils.toUnmodifiableSet())); + + this.jpaEmbeddables = Lazy.of(() -> metamodel.getEmbeddables().stream() // + .map(ManagedType::getJavaType) + .filter(it -> it != null) + .filter(it -> AnnotatedElementUtils.isAnnotated(it, Embeddable.class)) + .collect(StreamUtils.toUnmodifiableSet())); } public static JpaMetamodel of(Metamodel metamodel) { @@ -96,6 +111,27 @@ public boolean isSingleIdAttribute(Class entity, String name, Class attrib .orElse(false); } + /** + * Returns whether the given type is considered a mapped type, i.e. an actually JPA persisted entity, mapped + * superclass or native JPA embeddable. + * + * @param entity must not be {@literal null}. + * @return + */ + public boolean isMappedType(Class entity) { + + Assert.notNull(entity, "Type must not be null!"); + + if (!isJpaManaged(entity)) { + return false; + } + + ManagedType managedType = metamodel.managedType(entity); + + return !managedType.getPersistenceType().equals(PersistenceType.EMBEDDABLE) + || jpaEmbeddables.get().contains(entity); + } + /** * Wipes the static cache of {@link Metamodel} to {@link JpaMetamodel}. */ diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/JpaMetamodelUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/JpaMetamodelUnitTests.java index 37fc84555a..8aab83f2b0 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/JpaMetamodelUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/util/JpaMetamodelUnitTests.java @@ -18,10 +18,18 @@ import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; +import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import jakarta.persistence.metamodel.EntityType; +import jakarta.persistence.Embeddable; +import jakarta.persistence.Entity; +import jakarta.persistence.metamodel.EmbeddableType; +import jakarta.persistence.metamodel.EntityType; +import jakarta.persistence.metamodel.ManagedType; import jakarta.persistence.metamodel.Metamodel; +import jakarta.persistence.metamodel.Type.PersistenceType; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -60,4 +68,54 @@ void cacheIsEffectiveUnlessCleared() { JpaMetamodel.clear(); assertThat(model).isNotEqualTo(JpaMetamodel.of(metamodel)); } + + @Test // #2421 + void doesNotConsiderNonNativeEmbeddablesJpaManaged() { + + JpaMetamodel model = JpaMetamodel.of(metamodel); + + ManagedType entity = getEntity(Wrapper.class); + ManagedType embeddable = getEmbeddable(ExplicitEmbeddable.class); + ManagedType inner = getEmbeddable(Inner.class); + + doReturn(new HashSet<>(Arrays.asList(entity, embeddable, inner))).when(metamodel).getManagedTypes(); + doReturn(new HashSet<>(Arrays.asList(embeddable, inner))).when(metamodel).getEmbeddables(); + + assertThat(model.isMappedType(Wrapper.class)).isTrue(); + assertThat(model.isMappedType(ExplicitEmbeddable.class)).isTrue(); + assertThat(model.isMappedType(Inner.class)).isFalse(); + } + + private EmbeddableType getEmbeddable(Class type) { + + EmbeddableType managedType = getManagedType(type, EmbeddableType.class); + doReturn(PersistenceType.EMBEDDABLE).when(managedType).getPersistenceType(); + + return managedType; + } + + private EntityType getEntity(Class type) { + + EntityType managedType = getManagedType(type, EntityType.class); + doReturn(PersistenceType.ENTITY).when(managedType).getPersistenceType(); + + return managedType; + } + + private > T getManagedType(Class type, Class baseType) { + + T managedType = mock(baseType); + doReturn(type).when(managedType).getJavaType(); + doReturn(managedType).when(metamodel).managedType(type); + + return managedType; + } + + @Entity + static class Wrapper {} + + @Embeddable + static class ExplicitEmbeddable {} + + static class Inner {} }