diff --git a/pom.xml b/pom.xml index 5ba0fde025..18457886da 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-jpa-parent - 3.0.0-SNAPSHOT + 3.0.x-GH-2628-SNAPSHOT pom Spring Data JPA Parent diff --git a/spring-data-envers/pom.xml b/spring-data-envers/pom.xml index 540834bb60..6e6be0e0e7 100755 --- a/spring-data-envers/pom.xml +++ b/spring-data-envers/pom.xml @@ -5,12 +5,12 @@ org.springframework.data spring-data-envers - 3.0.0-SNAPSHOT + 3.0.x-GH-2628-SNAPSHOT org.springframework.data spring-data-jpa-parent - 3.0.0-SNAPSHOT + 3.0.x-GH-2628-SNAPSHOT ../pom.xml diff --git a/spring-data-jpa-distribution/pom.xml b/spring-data-jpa-distribution/pom.xml index bba8571672..724b2f2eec 100644 --- a/spring-data-jpa-distribution/pom.xml +++ b/spring-data-jpa-distribution/pom.xml @@ -14,7 +14,7 @@ org.springframework.data spring-data-jpa-parent - 3.0.0-SNAPSHOT + 3.0.x-GH-2628-SNAPSHOT ../pom.xml diff --git a/spring-data-jpa/pom.xml b/spring-data-jpa/pom.xml index 3ad9c39a2a..e995d2b343 100644 --- a/spring-data-jpa/pom.xml +++ b/spring-data-jpa/pom.xml @@ -6,7 +6,7 @@ org.springframework.data spring-data-jpa - 3.0.0-SNAPSHOT + 3.0.x-GH-2628-SNAPSHOT Spring Data JPA Spring Data module for JPA repositories. @@ -15,7 +15,7 @@ org.springframework.data spring-data-jpa-parent - 3.0.0-SNAPSHOT + 3.0.x-GH-2628-SNAPSHOT ../pom.xml diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtension.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtension.java index 07550f533c..e06e6abb0f 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtension.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtension.java @@ -33,6 +33,8 @@ import java.util.Optional; import java.util.Set; +import org.springframework.aot.generate.GenerationContext; +import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; @@ -44,6 +46,8 @@ import org.springframework.core.io.ResourceLoader; import org.springframework.dao.DataAccessException; import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor; +import org.springframework.data.aot.AotRepositoryContext; +import org.springframework.data.aot.RepositoryRegistrationAotProcessor; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.support.DefaultJpaContext; import org.springframework.data.jpa.repository.support.EntityManagerBeanDefinitionRegistrarPostProcessor; @@ -118,6 +122,11 @@ public void postProcess(BeanDefinitionBuilder builder, RepositoryConfigurationSo builder.addPropertyReference("mappingContext", JPA_MAPPING_CONTEXT_BEAN_NAME); } + @Override + public Class getRepositoryAotProcessor() { + return JpaRepositoryRegistrationAotProcessor.class; + } + /** * XML configurations do not support {@link Character} values. This method catches the exception thrown and returns an * {@link Optional#empty()} instead. @@ -285,4 +294,18 @@ static boolean isActive(@Nullable ClassLoader classLoader) { .anyMatch(agentClass -> ClassUtils.isPresent(agentClass, classLoader)); } } + + /** + * A {@link RepositoryRegistrationAotProcessor} implementation that maintains aot repository setup but skips domain + * type inspection which is handled by the core framework support for + * {@link org.springframework.orm.jpa.persistenceunit.PersistenceManagedTypes}. + * + * @since 3.0 + */ + public static class JpaRepositoryRegistrationAotProcessor extends RepositoryRegistrationAotProcessor { + + protected void contribute(AotRepositoryContext repositoryContext, GenerationContext generationContext) { + // don't register domain types nor annotations. + } + } } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtensionUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtensionUnitTests.java index d9d62f8da4..a3e4ac9eab 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtensionUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtensionUnitTests.java @@ -18,19 +18,18 @@ import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; -import java.util.Arrays; -import java.util.Collections; - import jakarta.persistence.EntityManagerFactory; import jakarta.persistence.metamodel.Metamodel; +import java.util.Arrays; +import java.util.Collections; + import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; - import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; import org.springframework.beans.factory.support.DefaultListableBeanFactory; @@ -141,6 +140,13 @@ public Class loadClass(String name) throws ClassNotFoundException { assertThat(classLoader).isNotInstanceOf(InspectionClassLoader.class); } + @Test // GH-2628 + void exposesJpaAotProcessor() { + + assertThat(new JpaRepositoryConfigExtension().getRepositoryAotProcessor()) + .isEqualTo(JpaRepositoryConfigExtension.JpaRepositoryRegistrationAotProcessor.class); + } + private void assertOnlyOnePersistenceAnnotationBeanPostProcessorRegistered(DefaultListableBeanFactory factory, String expectedBeanName) { diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/JpaRepositoryRegistrationAotProcessorUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/JpaRepositoryRegistrationAotProcessorUnitTests.java new file mode 100644 index 0000000000..5204de044c --- /dev/null +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/JpaRepositoryRegistrationAotProcessorUnitTests.java @@ -0,0 +1,128 @@ +/* + * Copyright 2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.repository.config; + +import static org.assertj.core.api.Assertions.*; + +import jakarta.persistence.Entity; + +import java.lang.annotation.Annotation; +import java.util.Collections; +import java.util.Set; + +import org.junit.jupiter.api.Test; +import org.springframework.aot.generate.ClassNameGenerator; +import org.springframework.aot.generate.DefaultGenerationContext; +import org.springframework.aot.generate.GenerationContext; +import org.springframework.aot.generate.InMemoryGeneratedFiles; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.core.annotation.MergedAnnotation; +import org.springframework.data.aot.AotRepositoryContext; +import org.springframework.data.repository.core.RepositoryInformation; + +/** + * @author Christoph Strobl + */ +class JpaRepositoryRegistrationAotProcessorUnitTests { + + @Test // GH-2628 + void aotProcessorMustNotRegisterDomainTypes() { + + GenerationContext ctx = new DefaultGenerationContext(new ClassNameGenerator(Object.class), + new InMemoryGeneratedFiles()); + + new JpaRepositoryConfigExtension.JpaRepositoryRegistrationAotProcessor() + .contribute(new DummyAotRepositoryContext() { + @Override + public Set> getResolvedTypes() { + return Collections.singleton(Person.class); + } + }, ctx); + + assertThat(RuntimeHintsPredicates.reflection().onType(Person.class)).rejects(ctx.getRuntimeHints()); + } + + @Test // GH-2628 + void aotProcessorMustNotRegisterAnnotations() { + + GenerationContext ctx = new DefaultGenerationContext(new ClassNameGenerator(Object.class), + new InMemoryGeneratedFiles()); + + new JpaRepositoryConfigExtension.JpaRepositoryRegistrationAotProcessor() + .contribute(new DummyAotRepositoryContext() { + + @Override + public Set> getResolvedAnnotations() { + + MergedAnnotation mergedAnnotation = MergedAnnotation.of(Entity.class); + return Set.of(mergedAnnotation); + } + }, ctx); + + assertThat(RuntimeHintsPredicates.reflection().onType(Entity.class)).rejects(ctx.getRuntimeHints()); + } + + static class Person {} + + static class DummyAotRepositoryContext implements AotRepositoryContext { + + @Override + public String getBeanName() { + return "jpaRepository"; + } + + @Override + public Set getBasePackages() { + return Collections.singleton(this.getClass().getPackageName()); + } + + @Override + public Set> getIdentifyingAnnotations() { + return Collections.singleton(Entity.class); + } + + @Override + public RepositoryInformation getRepositoryInformation() { + return null; + } + + @Override + public Set> getResolvedAnnotations() { + return null; + } + + @Override + public Set> getResolvedTypes() { + return null; + } + + @Override + public ConfigurableListableBeanFactory getBeanFactory() { + return null; + } + + @Override + public TypeIntrospector introspectType(String typeName) { + return null; + } + + @Override + public IntrospectedBeanDefinition introspectBeanDefinition(String beanName) { + return null; + } + } +}