diff --git a/src/main/java/org/springframework/data/jpa/mapping/JpaMetamodelMappingContext.java b/src/main/java/org/springframework/data/jpa/mapping/JpaMetamodelMappingContext.java index f6b8acf05..0450af7f3 100644 --- a/src/main/java/org/springframework/data/jpa/mapping/JpaMetamodelMappingContext.java +++ b/src/main/java/org/springframework/data/jpa/mapping/JpaMetamodelMappingContext.java @@ -22,6 +22,7 @@ import javax.persistence.metamodel.ManagedType; import javax.persistence.metamodel.Metamodel; import org.springframework.data.jpa.provider.PersistenceProvider; +import org.springframework.data.jpa.util.JpaMetamodel; import org.springframework.data.mapping.PersistentPropertyPaths; import org.springframework.data.mapping.context.AbstractMappingContext; import org.springframework.data.mapping.context.MappingContext; @@ -42,7 +43,7 @@ import org.springframework.util.Assert; public class JpaMetamodelMappingContext extends AbstractMappingContext, JpaPersistentProperty> { - private final Set models; + private final Metamodels models; private final PersistenceProvider persistenceProvider; /** @@ -55,7 +56,7 @@ public class JpaMetamodelMappingContext Assert.notNull(models, "JPA metamodel must not be null!"); Assert.notEmpty(models, "At least one JPA metamodel must be present!"); - this.models = models; + this.models = new Metamodels(models); this.persistenceProvider = PersistenceProvider.fromMetamodel(models.iterator().next()); } @@ -65,10 +66,7 @@ public class JpaMetamodelMappingContext */ @Override protected JpaPersistentEntityImpl createPersistentEntity(TypeInformation typeInformation) { - - Metamodel metamodel = getMetamodelFor(typeInformation.getType()); - - return new JpaPersistentEntityImpl(typeInformation, persistenceProvider, metamodel); + return new JpaPersistentEntityImpl(typeInformation, persistenceProvider, models.getMetamodel(typeInformation)); } /* @@ -87,7 +85,7 @@ public class JpaMetamodelMappingContext */ @Override protected boolean shouldCreatePersistentEntityFor(TypeInformation type) { - return getMetamodelFor(type.getType()) != null; + return models.isMetamodelManagedType(type); } /** @@ -103,32 +101,71 @@ public class JpaMetamodelMappingContext } /** - * Returns the {@link Metamodel} aware of the given type. + * A wrapper for a set of JPA {@link Metamodel} instances to simplify lookups of {@link JpaMetamodel} instances and + * managed type checks. * - * @param type must not be {@literal null}. - * @return can be {@literal null}. + * @author Oliver Gierke */ - @Nullable - private Metamodel getMetamodelFor(Class type) { + private static class Metamodels { - for (Metamodel model : models) { + private final Set metamodels; - try { - model.managedType(type); - return model; - } catch (IllegalArgumentException o_O) { + private Metamodels(Set metamodels) { + this.metamodels = metamodels; + } - // Fall back to inspect *all* managed types manually as Metamodel.managedType(…) only - // returns for entities, embeddables and managed supperclasses. + /** + * Returns the {@link JpaMetamodel} for the given type. + * + * @param type must not be {@literal null}. + * @return + */ + @Nullable + public JpaMetamodel getMetamodel(TypeInformation type) { - for (ManagedType managedType : model.getManagedTypes()) { - if (type.equals(managedType.getJavaType())) { - return model; + Metamodel metamodel = getMetamodelFor(type.getType()); + + return metamodel == null ? null : JpaMetamodel.of(metamodel); + } + + /** + * Retruns whether the given type is managed by one of the underlying {@link Metamodel} instances. + * + * @param type must not be {@literal null}. + * @return + */ + public boolean isMetamodelManagedType(TypeInformation type) { + return getMetamodelFor(type.getType()) != null; + } + + /** + * Returns the {@link Metamodel} aware of the given type. + * + * @param type must not be {@literal null}. + * @return can be {@literal null}. + */ + @Nullable + private Metamodel getMetamodelFor(Class type) { + + for (Metamodel model : metamodels) { + + try { + model.managedType(type); + return model; + } catch (IllegalArgumentException o_O) { + + // Fall back to inspect *all* managed types manually as Metamodel.managedType(…) only + // returns for entities, embeddables and managed supperclasses. + + for (ManagedType managedType : model.getManagedTypes()) { + if (type.equals(managedType.getJavaType())) { + return model; + } } } } - } - return null; + return null; + } } } diff --git a/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentEntityImpl.java b/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentEntityImpl.java index 7124136e8..18917134a 100644 --- a/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentEntityImpl.java +++ b/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentEntityImpl.java @@ -17,10 +17,9 @@ package org.springframework.data.jpa.mapping; import java.util.Comparator; -import javax.persistence.metamodel.Metamodel; - import org.springframework.data.annotation.Version; import org.springframework.data.jpa.provider.ProxyIdAccessor; +import org.springframework.data.jpa.util.JpaMetamodel; import org.springframework.data.mapping.IdentifierAccessor; import org.springframework.data.mapping.model.BasicPersistentEntity; import org.springframework.data.mapping.model.IdPropertyIdentifierAccessor; @@ -45,15 +44,17 @@ class JpaPersistentEntityImpl extends BasicPersistentEntity information, ProxyIdAccessor proxyIdAccessor, Metamodel metamodel) { + public JpaPersistentEntityImpl(TypeInformation information, ProxyIdAccessor proxyIdAccessor, + JpaMetamodel metamodel) { super(information, null); @@ -96,7 +97,7 @@ class JpaPersistentEntityImpl extends BasicPersistentEntity associationTargetType; private final boolean updateable; - private final JpaMetamodel metamodel; private final Lazy isIdProperty; private final Lazy isAssociation; + private final Lazy isEntity; /** * Creates a new {@link JpaPersistentPropertyImpl} @@ -107,7 +94,7 @@ class JpaPersistentPropertyImpl extends AnnotationBasedPersistentProperty owner, SimpleTypeHolder simpleTypeHolder) { super(property, owner, simpleTypeHolder); @@ -118,10 +105,10 @@ class JpaPersistentPropertyImpl extends AnnotationBasedPersistentProperty ID_ANNOTATIONS.stream().anyMatch(it -> isAnnotationPresent(it)) // - || this.metamodel.isSingleIdAttribute(getOwner().getType(), getName(), getType())); + || metamodel.isSingleIdAttribute(getOwner().getType(), getName(), getType())); + this.isEntity = Lazy.of(() -> metamodel.isJpaManaged(getActualType())); } /* @@ -160,7 +147,7 @@ class JpaPersistentPropertyImpl extends AnnotationBasedPersistentProperty extends JpaEntityInformationSu IdentifierDerivingDirectFieldAccessFallbackBeanWrapper(Class type, Metamodel metamodel) { super(type); this.metamodel = metamodel; - this.jpaMetamodel = new JpaMetamodel(metamodel); + this.jpaMetamodel = JpaMetamodel.of(metamodel); } /** diff --git a/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java b/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java index 3d58c890d..d26ba3354 100644 --- a/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java +++ b/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java @@ -246,7 +246,7 @@ public class JpaRepositoryFactory extends RepositoryFactorySupport { Assert.notNull(em, "EntityManager must not be null!"); - this.metamodel = new JpaMetamodel(em.getMetamodel()); + this.metamodel = JpaMetamodel.of(em.getMetamodel()); } /* diff --git a/src/main/java/org/springframework/data/jpa/util/JpaMetamodel.java b/src/main/java/org/springframework/data/jpa/util/JpaMetamodel.java index cc9a551c1..d64f6ae53 100644 --- a/src/main/java/org/springframework/data/jpa/util/JpaMetamodel.java +++ b/src/main/java/org/springframework/data/jpa/util/JpaMetamodel.java @@ -16,6 +16,8 @@ package org.springframework.data.jpa.util; import java.util.Collection; +import java.util.HashMap; +import java.util.Map; import java.util.Optional; import javax.persistence.metamodel.EntityType; @@ -35,6 +37,8 @@ import org.springframework.util.Assert; */ public class JpaMetamodel { + private static final Map CACHE = new HashMap<>(4); + private final Metamodel metamodel; private Lazy>> managedTypes; @@ -44,7 +48,7 @@ public class JpaMetamodel { * * @param metamodel must not be {@literal null}. */ - public JpaMetamodel(Metamodel metamodel) { + private JpaMetamodel(Metamodel metamodel) { Assert.notNull(metamodel, "Metamodel must not be null!"); @@ -55,6 +59,10 @@ public class JpaMetamodel { .collect(StreamUtils.toUnmodifiableSet())); } + public static JpaMetamodel of(Metamodel metamodel) { + return CACHE.computeIfAbsent(metamodel, JpaMetamodel::new); + } + /** * Returns whether the given type is managed by the backing JPA {@link Metamodel}. * diff --git a/src/test/java/org/springframework/data/jpa/mapping/JpaPersistentPropertyImplUnitTests.java b/src/test/java/org/springframework/data/jpa/mapping/JpaPersistentPropertyImplUnitTests.java index 1ab542281..4c8c09df4 100644 --- a/src/test/java/org/springframework/data/jpa/mapping/JpaPersistentPropertyImplUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/mapping/JpaPersistentPropertyImplUnitTests.java @@ -49,7 +49,7 @@ import org.springframework.data.util.TypeInformation; * @author Oliver Gierke * @author Greg Turnquist */ -@RunWith(MockitoJUnitRunner.class) +@RunWith(MockitoJUnitRunner.Silent.class) public class JpaPersistentPropertyImplUnitTests { @Mock Metamodel model; diff --git a/src/test/java/org/springframework/data/jpa/util/JpaMetamodelUnitTests.java b/src/test/java/org/springframework/data/jpa/util/JpaMetamodelUnitTests.java index 96e96fa50..36ae80cf9 100644 --- a/src/test/java/org/springframework/data/jpa/util/JpaMetamodelUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/util/JpaMetamodelUnitTests.java @@ -45,6 +45,6 @@ public class JpaMetamodelUnitTests { doReturn(Collections.singleton(type)).when(metamodel).getEntities(); - assertThat(new JpaMetamodel(metamodel).isSingleIdAttribute(Object.class, "id", Object.class)).isFalse(); + assertThat(JpaMetamodel.of(metamodel).isSingleIdAttribute(Object.class, "id", Object.class)).isFalse(); } }