Guard against Hibernate exposing non-embeddables as embeddables on versions > 5.4.21.
Hibernate 5.4.22 started including types managed by custom user types in the embeddables exposed via JPA's Metamodel API. This causes those to be considered types to explicitly map (read: unfold) in Spring Data REST. We now exposed a cleaned up view on this via a tweak in JpaPersistentPropertyImpl (ultimately in JpaMetamodel) looking for @Embeddable annotation on types allegedly considered embeddable by Hibernate. Fixes #2421.
This commit is contained in:
@@ -109,7 +109,7 @@ class JpaPersistentPropertyImpl extends AnnotationBasedPersistentProperty<JpaPer
|
||||
|
||||
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()));
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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 javax.persistence.Embeddable;
|
||||
import javax.persistence.metamodel.EntityType;
|
||||
import javax.persistence.metamodel.ManagedType;
|
||||
import javax.persistence.metamodel.Metamodel;
|
||||
import javax.persistence.metamodel.SingularAttribute;
|
||||
import javax.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 @@ import org.springframework.util.Assert;
|
||||
public class JpaMetamodel {
|
||||
|
||||
private static final Map<Metamodel, JpaMetamodel> CACHE = new ConcurrentHashMap<>(4);
|
||||
private static final Set<PersistenceType> ENTITY_OR_MAPPED_SUPERCLASS = EnumSet.of(PersistenceType.ENTITY,
|
||||
PersistenceType.MAPPED_SUPERCLASS);
|
||||
|
||||
private final Metamodel metamodel;
|
||||
|
||||
private Lazy<Collection<Class<?>>> managedTypes;
|
||||
private Lazy<Collection<Class<?>>> jpaEmbeddables;
|
||||
|
||||
/**
|
||||
* Creates a new {@link JpaMetamodel} for the given JPA {@link Metamodel}.
|
||||
@@ -54,10 +62,17 @@ public class JpaMetamodel {
|
||||
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 class JpaMetamodel {
|
||||
.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}.
|
||||
*/
|
||||
|
||||
@@ -18,10 +18,17 @@ package org.springframework.data.jpa.util;
|
||||
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 javax.persistence.Embeddable;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.metamodel.EmbeddableType;
|
||||
import javax.persistence.metamodel.EntityType;
|
||||
import javax.persistence.metamodel.ManagedType;
|
||||
import javax.persistence.metamodel.Metamodel;
|
||||
import javax.persistence.metamodel.Type.PersistenceType;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
@@ -60,4 +67,54 @@ class JpaMetamodelUnitTests {
|
||||
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 extends ManagedType<?>> T getManagedType(Class<?> type, Class<T> 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 {}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user