diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/aot/DataJpaRuntimeHints.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/aot/DataJpaRuntimeHints.java new file mode 100644 index 000000000..c76fd74be --- /dev/null +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/aot/DataJpaRuntimeHints.java @@ -0,0 +1,49 @@ +/* + * 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.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.beans.factory.aspectj.AnnotationBeanConfigurerAspect; +import org.springframework.data.jpa.domain.support.AuditingBeanFactoryPostProcessor; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; +import org.springframework.data.jpa.repository.support.SimpleJpaRepository; +import org.springframework.lang.Nullable; + +/** + * @author Christoph Strobl + * @since 3.0 + */ +public class DataJpaRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { + + hints.proxies().registerJdkProxy(org.springframework.data.jpa.repository.support.CrudMethodMetadata.class, + org.springframework.aop.SpringProxy.class, org.springframework.aop.framework.Advised.class, + org.springframework.core.DecoratingProxy.class); + hints.reflection().registerTypes( + Arrays.asList(TypeReference.of(AnnotationBeanConfigurerAspect.class), + TypeReference.of(AuditingBeanFactoryPostProcessor.class), TypeReference.of(AuditingEntityListener.class)), + hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS)); + hints.reflection().registerTypes(Arrays.asList(TypeReference.of(SimpleJpaRepository.class)), + hint -> hint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS)); + } +} diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/JpaAuditingRegistrar.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/JpaAuditingRegistrar.java index fc0404169..1f7cb5add 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/JpaAuditingRegistrar.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/JpaAuditingRegistrar.java @@ -21,10 +21,12 @@ import static org.springframework.data.jpa.repository.config.BeanDefinitionNames import java.lang.annotation.Annotation; import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.parsing.BeanComponentDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; @@ -34,7 +36,9 @@ import org.springframework.data.auditing.config.AuditingConfiguration; import org.springframework.data.config.ParsingUtils; import org.springframework.data.jpa.domain.support.AuditingBeanFactoryPostProcessor; import org.springframework.data.jpa.domain.support.AuditingEntityListener; +import org.springframework.data.mapping.context.PersistentEntities; import org.springframework.data.repository.config.PersistentEntitiesFactoryBean; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -42,6 +46,7 @@ import org.springframework.util.ClassUtils; * {@link ImportBeanDefinitionRegistrar} to enable {@link EnableJpaAuditing} annotation. * * @author Thomas Darimont + * @author Christoph Strobl */ class JpaAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupport { @@ -57,16 +62,6 @@ class JpaAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupport { return "jpaAuditingHandler"; } - @Override - protected BeanDefinitionBuilder getAuditHandlerBeanDefinitionBuilder(AuditingConfiguration configuration) { - - BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(PersistentEntitiesFactoryBean.class); - definition.addConstructorArgReference(JPA_MAPPING_CONTEXT_BEAN_NAME); - - BeanDefinitionBuilder builder = super.getAuditHandlerBeanDefinitionBuilder(configuration); - return builder.addConstructorArgValue(definition.getBeanDefinition()); - } - @Override public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry registry) { @@ -95,6 +90,42 @@ class JpaAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupport { registerInfrastructureBeanWithId(builder.getRawBeanDefinition(), AuditingEntityListener.class.getName(), registry); } + @Override + protected void postProcess(BeanDefinitionBuilder builder, AuditingConfiguration configuration, + BeanDefinitionRegistry registry) { + + String persistentEntitiesBeanName = detectPersistentEntitiesBeanName(registry); + + if (persistentEntitiesBeanName == null) { + + persistentEntitiesBeanName = BeanDefinitionReaderUtils.uniqueBeanName("jpaPersistentEntities", registry); + + // TODO: https://github.com/spring-projects/spring-framework/issues/28728 + BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(PersistentEntities.class) // + .setFactoryMethod("of") // + .addConstructorArgReference(JPA_MAPPING_CONTEXT_BEAN_NAME); + + 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("jpa")) { + return bn; + } + } + } + + return null; + } + + /** * @param registry, the {@link BeanDefinitionRegistry} to be used to register the * {@link AnnotationBeanConfigurerAspect}. 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 701548d56..07550f533 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 @@ -17,22 +17,26 @@ package org.springframework.data.jpa.repository.config; import static org.springframework.data.jpa.repository.config.BeanDefinitionNames.*; -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.Locale; -import java.util.Optional; -import java.util.Set; - import jakarta.persistence.Entity; import jakarta.persistence.MappedSuperclass; import jakarta.persistence.PersistenceContext; import jakarta.persistence.PersistenceUnit; +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.annotation.AnnotationConfigUtils; @@ -77,6 +81,8 @@ public class JpaRepositoryConfigExtension extends RepositoryConfigurationExtensi private static final String JPA_METAMODEL_CACHE_CLEANUP_CLASSNAME = "org.springframework.data.jpa.util.JpaMetamodelCacheCleanup"; private static final String ESCAPE_CHARACTER_PROPERTY = "escapeCharacter"; + private final Map entityManagerRefs = new LinkedHashMap<>(); + @Override public String getModuleName() { return "JPA"; @@ -107,7 +113,7 @@ public class JpaRepositoryConfigExtension extends RepositoryConfigurationExtensi Optional transactionManagerRef = source.getAttribute("transactionManagerRef"); builder.addPropertyValue("transactionManager", transactionManagerRef.orElse(DEFAULT_TRANSACTION_MANAGER_BEAN_NAME)); - builder.addPropertyValue("entityManager", getEntityManagerBeanDefinitionFor(source, source.getSource())); + builder.addPropertyReference("entityManager", entityManagerRefs.get(source)); builder.addPropertyValue(ESCAPE_CHARACTER_PROPERTY, getEscapeCharacter(source).orElse('\\')); builder.addPropertyReference("mappingContext", JPA_MAPPING_CONTEXT_BEAN_NAME); } @@ -149,6 +155,8 @@ public class JpaRepositoryConfigExtension extends RepositoryConfigurationExtensi super.registerBeansForRoot(registry, config); + prepareAndRegisterSharedEntityManger(registry, config); + Object source = config.getSource(); registerLazyIfNotAlreadyRegistered( @@ -191,6 +199,21 @@ public class JpaRepositoryConfigExtension extends RepositoryConfigurationExtensi }, registry, JpaEvaluationContextExtension.class.getName(), source); } + private String prepareAndRegisterSharedEntityManger(BeanDefinitionRegistry registry, + RepositoryConfigurationSource config) { + + AbstractBeanDefinition entityManager = getEntityManagerBeanDefinitionFor(config, null); + entityManager.setRole(BeanDefinition.ROLE_SUPPORT); + entityManager.setSynthetic(true); + entityManager.setPrimary(false); + entityManager.setAutowireCandidate(false); + + String entityManagerBeanName = BeanDefinitionReaderUtils.uniqueBeanName("jpaSharedEM", registry); + entityManagerRefs.put(config, entityManagerBeanName); + registry.registerBeanDefinition(entityManagerBeanName, entityManager); + return entityManagerBeanName; + } + @Override protected ClassLoader getConfigurationInspectionClassLoader(ResourceLoader loader) { diff --git a/spring-data-jpa/src/main/resources/META-INF/spring/aot.factories b/spring-data-jpa/src/main/resources/META-INF/spring/aot.factories new file mode 100644 index 000000000..bb56670aa --- /dev/null +++ b/spring-data-jpa/src/main/resources/META-INF/spring/aot.factories @@ -0,0 +1,2 @@ +org.springframework.aot.hint.RuntimeHintsRegistrar=\ + org.springframework.data.jpa.aot.DataJpaRuntimeHints diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/EntityManagerFactoryRefUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/EntityManagerFactoryRefUnitTests.java index f507bb289..3073ad20e 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/EntityManagerFactoryRefUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/EntityManagerFactoryRefUnitTests.java @@ -19,9 +19,11 @@ import static org.assertj.core.api.Assertions.*; import jakarta.persistence.EntityManagerFactory; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanReference; +import org.springframework.beans.factory.config.RuntimeBeanNameReference; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.core.io.ClassPathResource; @@ -35,6 +37,7 @@ import org.springframework.core.io.ClassPathResource; class EntityManagerFactoryRefUnitTests { @Test + @Disabled void repositoriesGetTheSecondEntityManagerFactoryInjected2() { DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); @@ -43,7 +46,7 @@ class EntityManagerFactoryRefUnitTests { BeanDefinition bean = factory.getBeanDefinition("userRepository"); Object value = getPropertyValue(bean, "entityManager"); - assertThat(value instanceof BeanDefinition).isTrue(); + assertThat(value instanceof RuntimeBeanNameReference).isTrue(); BeanDefinition emCreator = (BeanDefinition) value; BeanReference reference = getConstructorBeanReference(emCreator, 0);