diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/aot/DataJdbcRuntimeHints.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/aot/DataJdbcRuntimeHints.java new file mode 100644 index 00000000..8e6279e7 --- /dev/null +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/aot/DataJdbcRuntimeHints.java @@ -0,0 +1,56 @@ +/* + * 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.jdbc.aot; + +import java.util.Arrays; + +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.aot.hint.TypeReference; +import org.springframework.data.jdbc.repository.support.SimpleJdbcRepository; +import org.springframework.data.relational.auditing.RelationalAuditingCallback; +import org.springframework.data.relational.core.mapping.event.AfterConvertCallback; +import org.springframework.data.relational.core.mapping.event.AfterDeleteCallback; +import org.springframework.data.relational.core.mapping.event.AfterSaveCallback; +import org.springframework.data.relational.core.mapping.event.BeforeConvertCallback; +import org.springframework.data.relational.core.mapping.event.BeforeDeleteCallback; +import org.springframework.data.relational.core.mapping.event.BeforeSaveCallback; +import org.springframework.lang.Nullable; + +/** + * @author Christoph Strobl + * @since 3.0 + */ +public class DataJdbcRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { + + hints.reflection().registerTypes( + Arrays.asList(TypeReference.of(SimpleJdbcRepository.class), TypeReference.of(AfterConvertCallback.class), + TypeReference.of(AfterDeleteCallback.class), TypeReference.of(AfterSaveCallback.class), + TypeReference.of(BeforeConvertCallback.class), TypeReference.of(BeforeDeleteCallback.class), + TypeReference.of(BeforeSaveCallback.class), TypeReference.of(RelationalAuditingCallback.class)), + builder -> builder.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, + MemberCategory.INVOKE_PUBLIC_METHODS)); + + hints.proxies().registerJdkProxy(TypeReference.of("org.springframework.data.jdbc.core.convert.RelationResolver"), + TypeReference.of("org.springframework.aop.SpringProxy"), + TypeReference.of("org.springframework.aop.framework.Advised"), + TypeReference.of("org.springframework.core.DecoratingProxy")); + } +} diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/JdbcAuditingRegistrar.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/JdbcAuditingRegistrar.java index deb536ad..1456b994 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/JdbcAuditingRegistrar.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/JdbcAuditingRegistrar.java @@ -17,15 +17,19 @@ package org.springframework.data.jdbc.repository.config; import java.lang.annotation.Annotation; +import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.data.auditing.IsNewAwareAuditingHandler; import org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport; import org.springframework.data.auditing.config.AuditingConfiguration; +import org.springframework.data.config.ParsingUtils; +import org.springframework.data.mapping.context.PersistentEntities; import org.springframework.data.relational.auditing.RelationalAuditingCallback; -import org.springframework.data.repository.config.PersistentEntitiesFactoryBean; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -39,7 +43,6 @@ import org.springframework.util.Assert; class JdbcAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupport { private static final String AUDITING_HANDLER_BEAN_NAME = "jdbcAuditingHandler"; - private static final String JDBC_MAPPING_CONTEXT_BEAN_NAME = "jdbcMappingContext"; /** * {@inheritDoc} @@ -63,36 +66,69 @@ class JdbcAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupport { return AUDITING_HANDLER_BEAN_NAME; } + @Override + protected void postProcess(BeanDefinitionBuilder builder, AuditingConfiguration configuration, + BeanDefinitionRegistry registry) { + potentiallyRegisterJdbcPersistentEntities(builder, registry); + } + @Override protected BeanDefinitionBuilder getAuditHandlerBeanDefinitionBuilder(AuditingConfiguration configuration) { Assert.notNull(configuration, "AuditingConfiguration must not be null"); - BeanDefinitionBuilder builder = configureDefaultAuditHandlerAttributes(configuration, + return configureDefaultAuditHandlerAttributes(configuration, BeanDefinitionBuilder.rootBeanDefinition(IsNewAwareAuditingHandler.class)); - - - BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(PersistentEntitiesFactoryBean.class); - definition.addConstructorArgReference(JDBC_MAPPING_CONTEXT_BEAN_NAME); - - return builder.addConstructorArgValue(definition.getBeanDefinition()); } - /** - * Register the bean definition of {@link RelationalAuditingCallback}. {@inheritDoc} - * - * @see AuditingBeanDefinitionRegistrarSupport#registerAuditListenerBeanDefinition(BeanDefinition, - * BeanDefinitionRegistry) - */ @Override protected void registerAuditListenerBeanDefinition(BeanDefinition auditingHandlerDefinition, BeanDefinitionRegistry registry) { - Class listenerClass = RelationalAuditingCallback.class; - BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(listenerClass) // - .addConstructorArgReference(AUDITING_HANDLER_BEAN_NAME); + Assert.notNull(auditingHandlerDefinition, "BeanDefinition must not be null"); + Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); - registerInfrastructureBeanWithId(builder.getRawBeanDefinition(), listenerClass.getName(), registry); + BeanDefinitionBuilder listenerBeanDefinitionBuilder = BeanDefinitionBuilder + .rootBeanDefinition(RelationalAuditingCallback.class); + listenerBeanDefinitionBuilder + .addConstructorArgValue(ParsingUtils.getObjectFactoryBeanDefinition(AUDITING_HANDLER_BEAN_NAME, registry)); + + registerInfrastructureBeanWithId(listenerBeanDefinitionBuilder.getBeanDefinition(), + RelationalAuditingCallback.class.getName(), registry); + } + + static void potentiallyRegisterJdbcPersistentEntities(BeanDefinitionBuilder builder, + BeanDefinitionRegistry registry) { + + String persistentEntitiesBeanName = JdbcAuditingRegistrar.detectPersistentEntitiesBeanName(registry); + + if (persistentEntitiesBeanName == null) { + + persistentEntitiesBeanName = BeanDefinitionReaderUtils.uniqueBeanName("jdbcPersistentEntities", registry); + + // TODO: https://github.com/spring-projects/spring-framework/issues/28728 + BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(PersistentEntities.class) // + .setFactoryMethod("of") // + .addConstructorArgReference("jdbcMappingContext"); + + registry.registerBeanDefinition(persistentEntitiesBeanName, definition.getBeanDefinition()); + } + + builder.addConstructorArgReference(persistentEntitiesBeanName); + } + + @Nullable + private static String detectPersistentEntitiesBeanName(BeanDefinitionRegistry registry) { + + if (registry instanceof ListableBeanFactory beanFactory) { + for (String bn : beanFactory.getBeanNamesForType(PersistentEntities.class)) { + if (bn.startsWith("jdbc")) { + return bn; + } + } + } + + return null; } } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/JdbcRepositoryConfigExtension.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/JdbcRepositoryConfigExtension.java index c6444c23..4653ab1e 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/JdbcRepositoryConfigExtension.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/JdbcRepositoryConfigExtension.java @@ -56,6 +56,11 @@ public class JdbcRepositoryConfigExtension extends RepositoryConfigurationExtens return getModuleName().toLowerCase(Locale.US); } + @Override + public String getModuleIdentifier() { + return getModulePrefix(); + } + @Override public void postProcess(BeanDefinitionBuilder builder, RepositoryConfigurationSource source) { diff --git a/spring-data-jdbc/src/main/resources/META-INF/spring/aot.factories b/spring-data-jdbc/src/main/resources/META-INF/spring/aot.factories new file mode 100644 index 00000000..22800e12 --- /dev/null +++ b/spring-data-jdbc/src/main/resources/META-INF/spring/aot.factories @@ -0,0 +1,2 @@ +org.springframework.aot.hint.RuntimeHintsRegistrar=\ + org.springframework.data.jdbc.aot.DataJdbcRuntimeHints diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java index 63b4a078..324dc250 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java @@ -72,11 +72,11 @@ public class TestConfiguration { @Bean JdbcRepositoryFactory jdbcRepositoryFactory( @Qualifier("defaultDataAccessStrategy") DataAccessStrategy dataAccessStrategy, RelationalMappingContext context, - Dialect dialect, JdbcConverter converter, Optional namedQueries) { + Dialect dialect, JdbcConverter converter, Optional> namedQueries) { JdbcRepositoryFactory factory = new JdbcRepositoryFactory(dataAccessStrategy, context, converter, dialect, publisher, namedParameterJdbcTemplate()); - namedQueries.ifPresent(factory::setNamedQueries); + namedQueries.map(it -> it.iterator().next()).ifPresent(factory::setNamedQueries); return factory; } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/auditing/RelationalAuditingCallback.java b/spring-data-relational/src/main/java/org/springframework/data/relational/auditing/RelationalAuditingCallback.java index ab8f0ed7..38ec354c 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/auditing/RelationalAuditingCallback.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/auditing/RelationalAuditingCallback.java @@ -15,6 +15,7 @@ */ package org.springframework.data.relational.auditing; +import org.springframework.beans.factory.ObjectFactory; import org.springframework.context.ApplicationListener; import org.springframework.core.Ordered; import org.springframework.data.auditing.IsNewAwareAuditingHandler; @@ -41,13 +42,13 @@ public class RelationalAuditingCallback implements BeforeConvertCallback */ public static final int AUDITING_ORDER = 100; - private final IsNewAwareAuditingHandler handler; + private final ObjectFactory auditingHandlerFactory; - public RelationalAuditingCallback(IsNewAwareAuditingHandler handler) { + public RelationalAuditingCallback(ObjectFactory auditingHandlerFactory) { - Assert.notNull(handler, "Handler must not be null;"); + Assert.notNull(auditingHandlerFactory, "IsNewAwareAuditingHandler must not be null;"); - this.handler = handler; + this.auditingHandlerFactory = auditingHandlerFactory; } @Override @@ -57,6 +58,6 @@ public class RelationalAuditingCallback implements BeforeConvertCallback @Override public Object onBeforeConvert(Object entity) { - return handler.markAudited(entity); + return auditingHandlerFactory.getObject().markAudited(entity); } }