diff --git a/src/main/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtension.java b/src/main/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtension.java index d9e711644..81b065259 100644 --- a/src/main/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtension.java +++ b/src/main/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2013 the original author or authors. + * Copyright 2012-2014 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. @@ -22,10 +22,12 @@ 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.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.annotation.AnnotationConfigUtils; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.dao.DataAccessException; import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor; +import org.springframework.data.jpa.repository.support.EntityManagerBeanDefinitionRegistrarPostProcessor; import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean; import org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource; import org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport; @@ -137,13 +139,14 @@ public class JpaRepositoryConfigExtension extends RepositoryConfigurationExtensi super.registerBeansForRoot(registry, configurationSource); + Object source = configurationSource.getSource(); + registerWithSourceAndGeneratedBeanName(registry, new RootBeanDefinition( + EntityManagerBeanDefinitionRegistrarPostProcessor.class), source); + if (!hasBean(PAB_POST_PROCESSOR, registry) && !registry.containsBeanDefinition(AnnotationConfigUtils.PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)) { - AbstractBeanDefinition definition = BeanDefinitionBuilder.rootBeanDefinition(PAB_POST_PROCESSOR) - .getBeanDefinition(); - - registerWithSourceAndGeneratedBeanName(registry, definition, configurationSource.getSource()); + registerWithSourceAndGeneratedBeanName(registry, new RootBeanDefinition(PAB_POST_PROCESSOR), source); } } } diff --git a/src/main/java/org/springframework/data/jpa/repository/support/EntityManagerBeanDefinitionRegistrarPostProcessor.java b/src/main/java/org/springframework/data/jpa/repository/support/EntityManagerBeanDefinitionRegistrarPostProcessor.java new file mode 100644 index 000000000..b832e79fc --- /dev/null +++ b/src/main/java/org/springframework/data/jpa/repository/support/EntityManagerBeanDefinitionRegistrarPostProcessor.java @@ -0,0 +1,102 @@ +/* + * Copyright 2014 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 + * + * http://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.support; + +import static java.util.Arrays.*; +import static org.springframework.beans.factory.BeanFactoryUtils.*; + +import java.util.HashSet; +import java.util.Set; + +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.AutowireCandidateQualifier; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.orm.jpa.AbstractEntityManagerFactoryBean; +import org.springframework.orm.jpa.SharedEntityManagerCreator; + +/** + * {@link BeanFactoryPostProcessor} to register a {@link SharedEntityManagerCreator} for every + * {@link EntityManagerFactory} bean definition found in the application context to enable autowiring + * {@link EntityManager} instances into constructor arguments. Adds the {@link EntityManagerFactory} bean name as + * qualifier to the {@link EntityManager} {@link BeanDefinition} to enable explicit references in case of multiple + * {@link EntityManagerFactory} instances. + * + * @author Oliver Gierke + */ +public class EntityManagerBeanDefinitionRegistrarPostProcessor implements BeanFactoryPostProcessor { + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.config.BeanFactoryPostProcessor#postProcessBeanFactory(org.springframework.beans.factory.config.ConfigurableListableBeanFactory) + */ + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + + if (!(beanFactory instanceof BeanDefinitionRegistry)) { + return; + } + + for (String emfName : getEntityManagerFactoryBeanNames(beanFactory)) { + + BeanDefinitionBuilder builder = BeanDefinitionBuilder + .rootBeanDefinition("org.springframework.orm.jpa.SharedEntityManagerCreator"); + builder.setFactoryMethod("createSharedEntityManager"); + builder.addConstructorArgReference(emfName); + + AbstractBeanDefinition emBeanDefinition = builder.getRawBeanDefinition(); + AbstractBeanDefinition emfBeanDefinition = (AbstractBeanDefinition) beanFactory.getBeanDefinition(emfName); + + emBeanDefinition.addQualifier(new AutowireCandidateQualifier(Qualifier.class, emfName)); + emBeanDefinition.setScope(emfBeanDefinition.getScope()); + emBeanDefinition.setSource(emfBeanDefinition.getSource()); + + BeanDefinitionReaderUtils.registerWithGeneratedName(emBeanDefinition, (BeanDefinitionRegistry) beanFactory); + } + } + + /** + * Return all bean names for bean definitions that will result in an {@link EntityManagerFactory} eventually. We're + * checking for {@link EntityManagerFactory} and the well-known factory beans here to avoid eager initialization of + * the factory beans. The double lookup is necessary especially for JavaConfig scenarios as people might declare an + * {@link EntityManagerFactory} directly. + * + * @param beanFactory + * @return + */ + private static Iterable getEntityManagerFactoryBeanNames(ListableBeanFactory beanFactory) { + + Set names = new HashSet(); + names.addAll(asList(beanNamesForTypeIncludingAncestors(beanFactory, EntityManagerFactory.class, true, false))); + + for (String factoryBeanName : beanNamesForTypeIncludingAncestors(beanFactory, + AbstractEntityManagerFactoryBean.class, true, false)) { + names.add(factoryBeanName.substring(1)); + } + + return names; + } +} diff --git a/src/test/java/org/springframework/data/jpa/repository/config/InfrastructureConfig.java b/src/test/java/org/springframework/data/jpa/repository/config/InfrastructureConfig.java index 56ce58387..e12bb981b 100644 --- a/src/test/java/org/springframework/data/jpa/repository/config/InfrastructureConfig.java +++ b/src/test/java/org/springframework/data/jpa/repository/config/InfrastructureConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2014 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. @@ -32,6 +32,7 @@ import org.springframework.transaction.annotation.EnableTransactionManagement; * Reusable config class for testing pure java based Spring configuration. * * @author Thomas Darimont + * @author Oliver Gierke */ @Configuration @EnableTransactionManagement @@ -61,6 +62,7 @@ public class InfrastructureConfig { public LocalContainerEntityManagerFactoryBean entityManagerFactory() { LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); + em.setPersistenceUnitName("spring-data-jpa"); em.setDataSource(dataSource()); em.setJpaVendorAdapter(jpaVendorAdapter()); em.setPackagesToScan("purejavaconfig"); diff --git a/src/test/java/org/springframework/data/jpa/repository/config/JpaRepositoriesRegistrarIntegrationTests.java b/src/test/java/org/springframework/data/jpa/repository/config/JpaRepositoriesRegistrarIntegrationTests.java index 74bcf2b04..ba8db7127 100644 --- a/src/test/java/org/springframework/data/jpa/repository/config/JpaRepositoriesRegistrarIntegrationTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/config/JpaRepositoriesRegistrarIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2013 the original author or authors. + * Copyright 2012-2014 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. @@ -55,11 +55,9 @@ import org.springframework.util.ClassUtils; @ContextConfiguration public class JpaRepositoriesRegistrarIntegrationTests { - @Autowired - UserRepository repository; + @Autowired UserRepository repository; - @Autowired - SampleRepository sampleRepository; + @Autowired SampleRepository sampleRepository; @Configuration @EnableJpaRepositories(basePackages = "org.springframework.data.jpa.repository.sample") @@ -74,7 +72,7 @@ public class JpaRepositoriesRegistrarIntegrationTests { public EntityManagerFactory entityManagerFactory() { LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean(); factory.setDataSource(dataSource()); - factory.setPersistenceUnitName("default"); + factory.setPersistenceUnitName("spring-data-jpa"); factory.setJpaVendorAdapter(new HibernateJpaVendorAdapter()); factory.afterPropertiesSet(); return factory.getObject(); diff --git a/src/test/java/org/springframework/data/jpa/repository/support/EntityManagerBeanDefinitionRegistrarPostProcessorIntegrationTests.java b/src/test/java/org/springframework/data/jpa/repository/support/EntityManagerBeanDefinitionRegistrarPostProcessorIntegrationTests.java new file mode 100644 index 000000000..9f0ae7968 --- /dev/null +++ b/src/test/java/org/springframework/data/jpa/repository/support/EntityManagerBeanDefinitionRegistrarPostProcessorIntegrationTests.java @@ -0,0 +1,121 @@ +/* + * Copyright 2014 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 + * + * http://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.support; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import javax.persistence.EntityManager; +import javax.sql.DataSource; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.ComponentScan.Filter; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.ImportResource; +import org.springframework.orm.jpa.JpaVendorAdapter; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.stereotype.Component; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * Integration tests for {@link EntityManagerBeanDefinitionRegistrarPostProcessor}. + * + * @author Oliver Gierke + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +public class EntityManagerBeanDefinitionRegistrarPostProcessorIntegrationTests { + + @Configuration + @ImportResource("classpath:infrastructure.xml") + @ComponentScan(includeFilters = @Filter(TestComponent.class), useDefaultFilters = false) + static class Config { + + @Autowired DataSource dataSource; + @Autowired JpaVendorAdapter vendorAdapter; + + @Bean + public static EntityManagerBeanDefinitionRegistrarPostProcessor processor() { + return new EntityManagerBeanDefinitionRegistrarPostProcessor(); + } + + private LocalContainerEntityManagerFactoryBean emf() { + + LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean(); + factoryBean.setPersistenceUnitName("spring-data-jpa"); + factoryBean.setDataSource(dataSource); + factoryBean.setJpaVendorAdapter(vendorAdapter); + + return factoryBean; + } + + @Bean + LocalContainerEntityManagerFactoryBean firstEmf() { + return emf(); + } + + @Bean + LocalContainerEntityManagerFactoryBean secondEmf() { + return emf(); + } + } + + @Autowired EntityManagerInjectionTarget target; + + /** + * @see + */ + @Test + public void foo() { + + assertThat(target, is(notNullValue())); + assertThat(target.em, is(notNullValue())); + } + + @TestComponent + static class EntityManagerInjectionTarget { + + private final EntityManager em; + + @Autowired + public EntityManagerInjectionTarget(@Qualifier("firstEmf") EntityManager em) { + this.em = em; + } + } + + /** + * Annotation to demarcate test components. + * + * @author Oliver Gierke + */ + @Component + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + static @interface TestComponent { + + } +} diff --git a/src/test/resources/META-INF/persistence.xml b/src/test/resources/META-INF/persistence.xml index 4d7abaabb..4ada165df 100644 --- a/src/test/resources/META-INF/persistence.xml +++ b/src/test/resources/META-INF/persistence.xml @@ -1,6 +1,6 @@ - + org.springframework.data.jpa.domain.sample.AbstractMappedType org.springframework.data.jpa.domain.AbstractPersistable org.springframework.data.jpa.domain.AbstractAuditable @@ -91,5 +91,5 @@ - + diff --git a/src/test/resources/infrastructure.xml b/src/test/resources/infrastructure.xml index 37b3c5b91..370015f25 100644 --- a/src/test/resources/infrastructure.xml +++ b/src/test/resources/infrastructure.xml @@ -10,7 +10,7 @@ - +