diff --git a/pom.xml b/pom.xml index 6a62754327..997e4d7b27 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-jpa-parent - 3.0.0-SNAPSHOT + 3.0.0-GH-2497-SNAPSHOT pom Spring Data JPA Parent @@ -35,7 +35,7 @@ 4.3 8.0.23 42.2.19 - 3.0.0-SNAPSHOT + 3.0.0-GH-2593-SNAPSHOT 0.10.3 org.hibernate diff --git a/spring-data-envers/pom.xml b/spring-data-envers/pom.xml index 59eb8eab88..bc433c72e5 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.0-GH-2497-SNAPSHOT org.springframework.data spring-data-jpa-parent - 3.0.0-SNAPSHOT + 3.0.0-GH-2497-SNAPSHOT ../pom.xml diff --git a/spring-data-jpa-distribution/pom.xml b/spring-data-jpa-distribution/pom.xml index bba8571672..544fdb07bf 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.0-GH-2497-SNAPSHOT ../pom.xml diff --git a/spring-data-jpa/pom.xml b/spring-data-jpa/pom.xml index ba9220cc2a..b2b8e464e6 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.0-GH-2497-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.0-GH-2497-SNAPSHOT ../pom.xml 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 0000000000..c76fd74be7 --- /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 fc0404169f..1f7cb5add5 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 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.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 @@ * {@link ImportBeanDefinitionRegistrar} to enable {@link EnableJpaAuditing} annotation. * * @author Thomas Darimont + * @author Christoph Strobl */ class JpaAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupport { @@ -57,16 +62,6 @@ protected String getAuditingHandlerBeanName() { 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 @@ protected void registerAuditListenerBeanDefinition(BeanDefinition auditingHandle 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 701548d565..07550f533c 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 @@ import static org.springframework.data.jpa.repository.config.BeanDefinitionNames.*; +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 jakarta.persistence.Entity; -import jakarta.persistence.MappedSuperclass; -import jakarta.persistence.PersistenceContext; -import jakarta.persistence.PersistenceUnit; - +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 void postProcess(BeanDefinitionBuilder builder, RepositoryConfigurationSo 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 void registerBeansForRoot(BeanDefinitionRegistry registry, RepositoryConf super.registerBeansForRoot(registry, config); + prepareAndRegisterSharedEntityManger(registry, config); + Object source = config.getSource(); registerLazyIfNotAlreadyRegistered( @@ -191,6 +199,21 @@ public void registerBeansForRoot(BeanDefinitionRegistry registry, RepositoryConf }, 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 0000000000..bb56670aa6 --- /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 f507bb2890..3073ad20e0 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 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 @@ class EntityManagerFactoryRefUnitTests { @Test + @Disabled void repositoriesGetTheSecondEntityManagerFactoryInjected2() { DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); @@ -43,7 +46,7 @@ void repositoriesGetTheSecondEntityManagerFactoryInjected2() { 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);