Capture @EnableJpaRepositories configuration for AOT processing.
Closes #3838
This commit is contained in:
@@ -25,6 +25,7 @@ import java.util.Set;
|
||||
|
||||
import org.hibernate.jpa.HibernatePersistenceProvider;
|
||||
import org.junit.platform.commons.annotation.Testable;
|
||||
import org.mockito.Mockito;
|
||||
import org.openjdk.jmh.annotations.Benchmark;
|
||||
import org.openjdk.jmh.annotations.Fork;
|
||||
import org.openjdk.jmh.annotations.Level;
|
||||
@@ -37,16 +38,24 @@ import org.openjdk.jmh.annotations.Timeout;
|
||||
import org.openjdk.jmh.annotations.Warmup;
|
||||
|
||||
import org.springframework.aot.test.generate.TestGenerationContext;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.DefaultBeanNameGenerator;
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
import org.springframework.core.test.tools.TestCompiler;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.jpa.benchmark.model.Person;
|
||||
import org.springframework.data.jpa.benchmark.model.Profile;
|
||||
import org.springframework.data.jpa.benchmark.repository.PersonRepository;
|
||||
import org.springframework.data.jpa.repository.aot.JpaRepositoryContributor;
|
||||
import org.springframework.data.jpa.repository.aot.TestJpaAotRepositoryContext;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
import org.springframework.data.jpa.repository.sample.SampleConfig;
|
||||
import org.springframework.data.jpa.repository.support.JpaRepositoryFactory;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
import org.springframework.data.repository.core.support.RepositoryComposition;
|
||||
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
|
||||
@@ -74,7 +83,10 @@ public class AotRepositoryQueryMethodBenchmarks {
|
||||
|
||||
public static Class<?> aot;
|
||||
public static TestJpaAotRepositoryContext<PersonRepository> repositoryContext = new TestJpaAotRepositoryContext<>(
|
||||
PersonRepository.class, null);
|
||||
PersonRepository.class, null,
|
||||
new AnnotationRepositoryConfigurationSource(AnnotationMetadata.introspect(SampleConfig.class),
|
||||
EnableJpaRepositories.class, new DefaultResourceLoader(), new StandardEnvironment(),
|
||||
Mockito.mock(BeanDefinitionRegistry.class), DefaultBeanNameGenerator.INSTANCE));
|
||||
|
||||
EntityManager entityManager;
|
||||
RepositoryComposition.RepositoryFragments fragments;
|
||||
|
||||
@@ -58,8 +58,16 @@ class AotMetamodel implements Metamodel {
|
||||
private final Lazy<EntityManager> entityManager = Lazy.of(() -> getEntityManagerFactory().createEntityManager());
|
||||
|
||||
public AotMetamodel(AotRepositoryContext repositoryContext) {
|
||||
this(repositoryContext.getResolvedTypes().stream().map(Class::getName)
|
||||
.filter(name -> !name.startsWith("jakarta.persistence")).toList(), null);
|
||||
this(repositoryContext.getResolvedTypes().stream().filter(AotMetamodel::isJakartaAnnotated).map(Class::getName)
|
||||
.toList(), null);
|
||||
}
|
||||
|
||||
private static boolean isJakartaAnnotated(Class<?> cls) {
|
||||
|
||||
return cls.isAnnotationPresent(jakarta.persistence.Entity.class)
|
||||
|| cls.isAnnotationPresent(jakarta.persistence.Embeddable.class)
|
||||
|| cls.isAnnotationPresent(jakarta.persistence.MappedSuperclass.class)
|
||||
|| cls.isAnnotationPresent(jakarta.persistence.Converter.class);
|
||||
}
|
||||
|
||||
public AotMetamodel(PersistenceManagedTypes managedTypes) {
|
||||
|
||||
@@ -22,9 +22,11 @@ import jakarta.persistence.spi.PersistenceUnitInfo;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.MergedAnnotation;
|
||||
import org.springframework.core.annotation.MergedAnnotations;
|
||||
@@ -70,6 +72,7 @@ public class JpaRepositoryContributor extends RepositoryContributor {
|
||||
private final PersistenceProvider persistenceProvider;
|
||||
private final QueriesFactory queriesFactory;
|
||||
private final EntityGraphLookup entityGraphLookup;
|
||||
private final AotRepositoryContext context;
|
||||
|
||||
public JpaRepositoryContributor(AotRepositoryContext repositoryContext) {
|
||||
this(repositoryContext, new AotMetamodel(repositoryContext));
|
||||
@@ -87,9 +90,10 @@ public class JpaRepositoryContributor extends RepositoryContributor {
|
||||
|
||||
super(repositoryContext);
|
||||
|
||||
this.context = repositoryContext;
|
||||
this.metamodel = entityManagerFactory.getMetamodel();
|
||||
this.persistenceProvider = PersistenceProvider.fromEntityManagerFactory(entityManagerFactory);
|
||||
this.queriesFactory = new QueriesFactory(entityManagerFactory);
|
||||
this.queriesFactory = new QueriesFactory(repositoryContext.getConfigurationSource(), entityManagerFactory);
|
||||
this.entityGraphLookup = new EntityGraphLookup(entityManagerFactory);
|
||||
}
|
||||
|
||||
@@ -97,9 +101,11 @@ public class JpaRepositoryContributor extends RepositoryContributor {
|
||||
|
||||
super(repositoryContext);
|
||||
|
||||
this.context = repositoryContext;
|
||||
this.metamodel = metamodel;
|
||||
this.persistenceProvider = PersistenceProvider.fromEntityManagerFactory(metamodel.getEntityManagerFactory());
|
||||
this.queriesFactory = new QueriesFactory(metamodel.getEntityManagerFactory(), metamodel);
|
||||
this.queriesFactory = new QueriesFactory(repositoryContext.getConfigurationSource(),
|
||||
metamodel.getEntityManagerFactory(), metamodel);
|
||||
this.entityGraphLookup = new EntityGraphLookup(metamodel.getEntityManagerFactory());
|
||||
}
|
||||
|
||||
@@ -111,25 +117,36 @@ public class JpaRepositoryContributor extends RepositoryContributor {
|
||||
@Override
|
||||
protected void customizeConstructor(AotRepositoryConstructorBuilder constructorBuilder) {
|
||||
|
||||
// TODO: BeanFactoryQueryRewriterProvider if there is a method using QueryRewriters.
|
||||
|
||||
constructorBuilder.addParameter("entityManager", EntityManager.class);
|
||||
constructorBuilder.addParameter("context", RepositoryFactoryBeanSupport.FragmentCreationContext.class);
|
||||
|
||||
// TODO: Pick up the configured QueryEnhancerSelector
|
||||
Optional<Class<QueryEnhancerSelector>> queryEnhancerSelector = getQueryEnhancerSelectorClass();
|
||||
|
||||
constructorBuilder.customize(builder -> {
|
||||
builder.addStatement("super($T.DEFAULT_SELECTOR, context)", QueryEnhancerSelector.class);
|
||||
|
||||
if (queryEnhancerSelector.isPresent()) {
|
||||
builder.addStatement("super(new T$(), context)", queryEnhancerSelector.get());
|
||||
} else {
|
||||
builder.addStatement("super($T.DEFAULT_SELECTOR, context)", QueryEnhancerSelector.class);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private Optional<Class<QueryEnhancerSelector>> getQueryEnhancerSelectorClass() {
|
||||
return (Optional) context.getConfigurationSource().getAttribute("queryEnhancerSelector", Class.class)
|
||||
.filter(it -> !it.equals(QueryEnhancerSelector.DefaultQueryEnhancerSelector.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable MethodContributor<? extends QueryMethod> contributeQueryMethod(Method method) {
|
||||
|
||||
JpaQueryMethod queryMethod = new JpaQueryMethod(method, getRepositoryInformation(), getProjectionFactory(),
|
||||
persistenceProvider);
|
||||
|
||||
// meh!
|
||||
QueryEnhancerSelector selector = QueryEnhancerSelector.DEFAULT_SELECTOR;
|
||||
Optional<Class<QueryEnhancerSelector>> queryEnhancerSelectorClass = getQueryEnhancerSelectorClass();
|
||||
QueryEnhancerSelector selector = queryEnhancerSelectorClass.map(BeanUtils::instantiateClass)
|
||||
.orElse(QueryEnhancerSelector.DEFAULT_SELECTOR);
|
||||
|
||||
// no stored procedures for now.
|
||||
if (queryMethod.isProcedureQuery()) {
|
||||
@@ -183,7 +200,6 @@ public class JpaRepositoryContributor extends RepositoryContributor {
|
||||
TypeInformation<?> returnType = getRepositoryInformation().getReturnType(method);
|
||||
|
||||
boolean returnsCount = JpaCodeBlocks.QueryExecutionBlockBuilder.returnsModifying(returnType.getType());
|
||||
|
||||
boolean isVoid = ClassUtils.isVoidType(returnType.getType());
|
||||
|
||||
if (!returnsCount && !isVoid) {
|
||||
|
||||
@@ -24,6 +24,7 @@ import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
@@ -37,6 +38,7 @@ import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.jpa.repository.query.*;
|
||||
import org.springframework.data.jpa.repository.support.JpqlQueryTemplates;
|
||||
import org.springframework.data.repository.aot.generate.AotQueryMethodGenerationContext;
|
||||
import org.springframework.data.repository.config.RepositoryConfigurationSource;
|
||||
import org.springframework.data.repository.core.RepositoryInformation;
|
||||
import org.springframework.data.repository.query.ReturnedType;
|
||||
import org.springframework.data.repository.query.parser.PartTree;
|
||||
@@ -53,14 +55,21 @@ class QueriesFactory {
|
||||
|
||||
private final EntityManagerFactory entityManagerFactory;
|
||||
private final Metamodel metamodel;
|
||||
private final EscapeCharacter escapeCharacter;
|
||||
private final JpqlQueryTemplates templates = JpqlQueryTemplates.UPPER;
|
||||
|
||||
public QueriesFactory(EntityManagerFactory entityManagerFactory) {
|
||||
this(entityManagerFactory, entityManagerFactory.getMetamodel());
|
||||
public QueriesFactory(RepositoryConfigurationSource configurationSource, EntityManagerFactory entityManagerFactory) {
|
||||
this(configurationSource, entityManagerFactory, entityManagerFactory.getMetamodel());
|
||||
}
|
||||
|
||||
public QueriesFactory(EntityManagerFactory entityManagerFactory, Metamodel metamodel) {
|
||||
public QueriesFactory(RepositoryConfigurationSource configurationSource, EntityManagerFactory entityManagerFactory,
|
||||
Metamodel metamodel) {
|
||||
|
||||
this.metamodel = metamodel;
|
||||
this.entityManagerFactory = entityManagerFactory;
|
||||
|
||||
Optional<Character> escapeCharacter = configurationSource.getAttribute("escapeCharacter", Character.class);
|
||||
this.escapeCharacter = escapeCharacter.map(EscapeCharacter::of).orElse(EscapeCharacter.DEFAULT);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,8 +86,7 @@ class QueriesFactory {
|
||||
QueryEnhancerSelector selector, JpaQueryMethod queryMethod, ReturnedType returnedType) {
|
||||
|
||||
if (query.isPresent() && StringUtils.hasText(query.getString("value"))) {
|
||||
return buildStringQuery(repositoryInformation.getDomainType(), returnedType, selector, query,
|
||||
queryMethod);
|
||||
return buildStringQuery(repositoryInformation.getDomainType(), returnedType, selector, query, queryMethod);
|
||||
}
|
||||
|
||||
TypedQueryReference<?> namedQuery = getNamedQuery(returnedType, queryMethod.getNamedQueryName());
|
||||
@@ -201,9 +209,6 @@ class QueriesFactory {
|
||||
MergedAnnotation<Query> query, JpaQueryMethod queryMethod) {
|
||||
|
||||
PartTree partTree = new PartTree(queryMethod.getName(), repositoryInformation.getDomainType());
|
||||
// TODO make configurable
|
||||
JpqlQueryTemplates templates = JpqlQueryTemplates.UPPER;
|
||||
|
||||
AotQuery aotQuery = createQuery(partTree, returnedType, queryMethod.getParameters(), templates);
|
||||
|
||||
if (query.isPresent() && StringUtils.hasText(query.getString("countQuery"))) {
|
||||
@@ -222,8 +227,7 @@ class QueriesFactory {
|
||||
private AotQuery createQuery(PartTree partTree, ReturnedType returnedType, JpaParameters parameters,
|
||||
JpqlQueryTemplates templates) {
|
||||
|
||||
ParameterMetadataProvider metadataProvider = new ParameterMetadataProvider(parameters, EscapeCharacter.DEFAULT,
|
||||
templates);
|
||||
ParameterMetadataProvider metadataProvider = new ParameterMetadataProvider(parameters, escapeCharacter, templates);
|
||||
JpaQueryCreator queryCreator = new JpaQueryCreator(partTree, false, returnedType, metadataProvider, templates,
|
||||
metamodel);
|
||||
|
||||
@@ -234,8 +238,7 @@ class QueriesFactory {
|
||||
private AotQuery createCountQuery(PartTree partTree, ReturnedType returnedType, JpaParameters parameters,
|
||||
JpqlQueryTemplates templates) {
|
||||
|
||||
ParameterMetadataProvider metadataProvider = new ParameterMetadataProvider(parameters, EscapeCharacter.DEFAULT,
|
||||
templates);
|
||||
ParameterMetadataProvider metadataProvider = new ParameterMetadataProvider(parameters, escapeCharacter, templates);
|
||||
JpaQueryCreator queryCreator = new JpaCountQueryCreator(partTree, returnedType, metadataProvider, templates,
|
||||
metamodel);
|
||||
|
||||
|
||||
@@ -69,7 +69,6 @@ import org.springframework.data.repository.config.RepositoryConfigurationSource;
|
||||
import org.springframework.data.repository.config.RepositoryRegistrationAotProcessor;
|
||||
import org.springframework.data.repository.config.XmlRepositoryConfigurationSource;
|
||||
import org.springframework.orm.jpa.persistenceunit.PersistenceManagedTypes;
|
||||
import org.springframework.orm.jpa.persistenceunit.PersistenceUnitManager;
|
||||
import org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -339,38 +338,34 @@ public class JpaRepositoryConfigExtension extends RepositoryConfigurationExtensi
|
||||
|
||||
Environment environment = repositoryContext.getEnvironment();
|
||||
|
||||
boolean enabled = Boolean.parseBoolean(
|
||||
environment.getProperty(AotContext.GENERATED_REPOSITORIES_ENABLED, "false"));
|
||||
boolean enabled = Boolean
|
||||
.parseBoolean(environment.getProperty(AotContext.GENERATED_REPOSITORIES_ENABLED, "false"));
|
||||
if (!enabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ConfigurableListableBeanFactory beanFactory = repositoryContext.getBeanFactory();
|
||||
|
||||
boolean useEntityManager = Boolean.parseBoolean(
|
||||
environment.getProperty(GENERATED_REPOSITORIES_JPA_USE_ENTITY_MANAGER, "false"));
|
||||
boolean useEntityManager = Boolean
|
||||
.parseBoolean(environment.getProperty(GENERATED_REPOSITORIES_JPA_USE_ENTITY_MANAGER, "false"));
|
||||
|
||||
if (useEntityManager) {
|
||||
|
||||
ObjectProvider<PersistenceUnitManager> unitManagerProvider = beanFactory
|
||||
.getBeanProvider(PersistenceUnitManager.class);
|
||||
PersistenceUnitManager unitManager = unitManagerProvider.getIfAvailable();
|
||||
Optional<String> entityManagerFactoryRef = repositoryContext.getConfigurationSource()
|
||||
.getAttribute("entityManagerFactoryRef");
|
||||
|
||||
if (unitManager != null) {
|
||||
log.debug(
|
||||
"Using EntityManager '%s' for AOT repository generation".formatted(entityManagerFactoryRef.orElse("")));
|
||||
|
||||
log.debug("Using PersistenceUnitManager for AOT repository generation");
|
||||
return new JpaRepositoryContributor(repositoryContext, unitManager.obtainDefaultPersistenceUnitInfo());
|
||||
}
|
||||
|
||||
log.debug("Using EntityManager for AOT repository generation");
|
||||
|
||||
EntityManagerFactory emf = beanFactory.getBean(EntityManagerFactory.class);
|
||||
EntityManagerFactory emf = entityManagerFactoryRef
|
||||
.map(it -> beanFactory.getBean(it, EntityManagerFactory.class))
|
||||
.orElseGet(() -> beanFactory.getBean(EntityManagerFactory.class));
|
||||
return new JpaRepositoryContributor(repositoryContext, emf);
|
||||
}
|
||||
|
||||
ObjectProvider<PersistenceManagedTypes> managedTypesProvider = beanFactory
|
||||
.getBeanProvider(PersistenceManagedTypes.class);
|
||||
PersistenceManagedTypes managedTypes = managedTypesProvider.getIfAvailable();
|
||||
PersistenceManagedTypes managedTypes = managedTypesProvider.getIfUnique();
|
||||
|
||||
if (managedTypes != null) {
|
||||
|
||||
@@ -379,7 +374,7 @@ public class JpaRepositoryConfigExtension extends RepositoryConfigurationExtensi
|
||||
}
|
||||
|
||||
ObjectProvider<PersistenceUnitInfo> infoProvider = beanFactory.getBeanProvider(PersistenceUnitInfo.class);
|
||||
PersistenceUnitInfo unitInfo = infoProvider.getIfAvailable();
|
||||
PersistenceUnitInfo unitInfo = infoProvider.getIfUnique();
|
||||
|
||||
if (unitInfo != null) {
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ import jakarta.persistence.EntityManagerFactory;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import org.springframework.aot.test.generate.TestGenerationContext;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
@@ -29,11 +31,18 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.DefaultBeanNameGenerator;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ImportResource;
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
import org.springframework.core.test.tools.TestCompiler;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
import org.springframework.data.jpa.repository.sample.SampleConfig;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
|
||||
import org.springframework.data.repository.query.ValueExpressionDelegate;
|
||||
@@ -56,8 +65,15 @@ class AotFragmentTestConfigurationSupport implements BeanFactoryPostProcessor {
|
||||
private final TestJpaAotRepositoryContext<?> repositoryContext;
|
||||
|
||||
public AotFragmentTestConfigurationSupport(Class<?> repositoryInterface) {
|
||||
this(repositoryInterface, SampleConfig.class);
|
||||
}
|
||||
|
||||
public AotFragmentTestConfigurationSupport(Class<?> repositoryInterface, Class<?> configClass) {
|
||||
this.repositoryInterface = repositoryInterface;
|
||||
this.repositoryContext = new TestJpaAotRepositoryContext<>(repositoryInterface, null);
|
||||
this.repositoryContext = new TestJpaAotRepositoryContext<>(repositoryInterface, null,
|
||||
new AnnotationRepositoryConfigurationSource(AnnotationMetadata.introspect(configClass),
|
||||
EnableJpaRepositories.class, new DefaultResourceLoader(), new StandardEnvironment(),
|
||||
Mockito.mock(BeanDefinitionRegistry.class), DefaultBeanNameGenerator.INSTANCE));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2025 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.aot;
|
||||
|
||||
import static net.javacrumbs.jsonunit.assertj.JsonAssertions.*;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.UrlResource;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
|
||||
/**
|
||||
* Integration tests for the {@link UserRepository} AOT fragment.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
class JpaRepositoryContributorConfigurationTests {
|
||||
|
||||
@Configuration
|
||||
static class JpaRepositoryContributorConfiguration extends AotFragmentTestConfigurationSupport {
|
||||
public JpaRepositoryContributorConfiguration() {
|
||||
super(UserRepository.class, MyConfiguration.class);
|
||||
}
|
||||
|
||||
@EnableJpaRepositories(escapeCharacter = 'ö', /* avoid creating repository instances */ includeFilters = {
|
||||
@ComponentScan.Filter(value = EnableJpaRepositories.class) })
|
||||
static class MyConfiguration {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Test // GH-3838
|
||||
void shouldConsiderConfiguration() throws IOException {
|
||||
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
|
||||
context.register(JpaRepositoryContributorConfiguration.class);
|
||||
context.refreshForAotProcessing(new RuntimeHints());
|
||||
|
||||
String location = UserRepository.class.getPackageName().replace('.', '/') + "/"
|
||||
+ UserRepository.class.getSimpleName() + ".json";
|
||||
UrlResource resource = new UrlResource(context.getBeanFactory().getBeanClassLoader().getResource(location));
|
||||
|
||||
assertThat(resource).isNotNull();
|
||||
assertThat(resource.exists()).isTrue();
|
||||
|
||||
String json = resource.getContentAsString(StandardCharsets.UTF_8);
|
||||
|
||||
assertThatJson(json).inPath("$.methods[?(@.name == 'streamByLastnameLike')].query").isArray().first().isObject()
|
||||
.containsEntry("query",
|
||||
"SELECT u FROM org.springframework.data.jpa.domain.sample.User u WHERE u.lastname LIKE :lastname ESCAPE 'ö'");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import org.springframework.core.test.tools.ClassFile;
|
||||
import org.springframework.data.jpa.domain.sample.Role;
|
||||
import org.springframework.data.jpa.domain.sample.User;
|
||||
import org.springframework.data.repository.config.AotRepositoryContext;
|
||||
import org.springframework.data.repository.config.RepositoryConfigurationSource;
|
||||
import org.springframework.data.repository.core.RepositoryInformation;
|
||||
import org.springframework.data.repository.core.support.RepositoryComposition;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -45,9 +46,12 @@ public class TestJpaAotRepositoryContext<T> implements AotRepositoryContext {
|
||||
|
||||
private final StubRepositoryInformation repositoryInformation;
|
||||
private final Class<T> repositoryInterface;
|
||||
private final RepositoryConfigurationSource configurationSource;
|
||||
|
||||
public TestJpaAotRepositoryContext(Class<T> repositoryInterface, @Nullable RepositoryComposition composition) {
|
||||
public TestJpaAotRepositoryContext(Class<T> repositoryInterface, @Nullable RepositoryComposition composition,
|
||||
RepositoryConfigurationSource configurationSource) {
|
||||
this.repositoryInterface = repositoryInterface;
|
||||
this.configurationSource = configurationSource;
|
||||
this.repositoryInformation = new StubRepositoryInformation(repositoryInterface, composition);
|
||||
}
|
||||
|
||||
@@ -85,6 +89,11 @@ public class TestJpaAotRepositoryContext<T> implements AotRepositoryContext {
|
||||
return "JPA";
|
||||
}
|
||||
|
||||
@Override
|
||||
public RepositoryConfigurationSource getConfigurationSource() {
|
||||
return configurationSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getBasePackages() {
|
||||
return Set.of("org.springframework.data.dummy.repository.aot");
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package org.springframework.data.jpa.repository.config;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
@@ -46,6 +47,7 @@ import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.config.AotRepositoryContext;
|
||||
import org.springframework.data.repository.config.AotRepositoryInformation;
|
||||
import org.springframework.data.repository.config.RepositoryConfigurationSource;
|
||||
import org.springframework.data.repository.core.RepositoryInformation;
|
||||
import org.springframework.data.repository.core.support.AbstractRepositoryMetadata;
|
||||
import org.springframework.javapoet.ClassName;
|
||||
@@ -155,6 +157,11 @@ class JpaRepositoryRegistrationAotProcessorUnitTests {
|
||||
return "JPA";
|
||||
}
|
||||
|
||||
@Override
|
||||
public RepositoryConfigurationSource getConfigurationSource() {
|
||||
return mock(RepositoryConfigurationSource.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getBasePackages() {
|
||||
return Collections.singleton(this.getClass().getPackageName());
|
||||
|
||||
@@ -53,6 +53,12 @@ For instance, profiles that have been enabled at build-time are automatically en
|
||||
Also, the Spring Data module implementing a repository is fixed.
|
||||
Changing the implementation requires AOT re-processing.
|
||||
|
||||
NOTE: AOT processing avoids database access.
|
||||
Therefore, it initializes an in-memory Hibernate instance for metadata collection.
|
||||
Types for the Hibernate configuration are determined by our AOT metadata collector.
|
||||
We prefer using a `PersistentEntityTypes` bean if available and fall back to `PersistenceUnitInfo` or our own discovered types.
|
||||
If our type scanning is not sufficient for your arrangement, you can enable direct `EntityManagerFactory` usage by configuring the `spring.aot.jpa.repositories.use-entitymanager=true` property.
|
||||
|
||||
=== Eligible Methods
|
||||
|
||||
AOT repositories filter methods that are eligible for AOT processing.
|
||||
@@ -69,7 +75,6 @@ These are typically all query methods that are not backed by an xref:repositorie
|
||||
* Value Expressions (Those require a bit of reflective information.
|
||||
Mind that using Value Expressions requires expression parsing and contextual information to evaluate the expression)
|
||||
|
||||
|
||||
**Limitations**
|
||||
|
||||
* Requires Hibernate for AOT processing.
|
||||
@@ -79,8 +84,7 @@ Mind that using Value Expressions requires expression parsing and contextual inf
|
||||
|
||||
**Excluded methods**
|
||||
|
||||
* `CrudRepository` and other base interface methods
|
||||
* Querydsl and Query by Example methods
|
||||
* `CrudRepository`, Querydsl, Query by Example, and other base interface methods as their implementation is provided by the base class respective fragments
|
||||
* Methods whose implementation would be overly complex
|
||||
** Methods accepting `ScrollPosition` (e.g. `Keyset` pagination)
|
||||
** Stored procedure query methods annotated with `@Procedure`
|
||||
|
||||
Reference in New Issue
Block a user