Allow Data JPA's bootstrap mode to be configured via the environment

In Spring Data Lovelace, repositories' bootstrap mode can be
configured via @EnableJpaRepositories. This commit adds support for
configuring the mode via the environment rather than having to use
the annotation. Additionally, when deferred or lazy bootstrapping is
being used, the LocalContainerEntityManagerFactoryBean is configured
to use a bootstrap executor. This allows JPA's initialization to be
performed on a separate thread, allowing the rest of application
context initialization to proceed in parallel.

Closes gh-13833
pull/14030/merge
Andy Wilkinson 6 years ago
parent ab1f5931a0
commit f28528a527

@ -31,6 +31,7 @@ import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.StandardAnnotationMetadata;
import org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource;
import org.springframework.data.repository.config.BootstrapMode;
import org.springframework.data.repository.config.RepositoryConfigurationDelegate;
import org.springframework.data.repository.config.RepositoryConfigurationExtension;
import org.springframework.data.util.Streamable;
@ -72,6 +73,13 @@ public abstract class AbstractRepositoryConfigurationSourceSupport
return AbstractRepositoryConfigurationSourceSupport.this
.getBasePackages();
}
@Override
public BootstrapMode getBootstrapMode() {
return AbstractRepositoryConfigurationSourceSupport.this
.getBootstrapMode();
}
};
}
@ -97,6 +105,15 @@ public abstract class AbstractRepositoryConfigurationSourceSupport
*/
protected abstract RepositoryConfigurationExtension getRepositoryConfigurationExtension();
/**
* The {@link BootstrapMode} for the particular repository support. Defaults to
* {@link BootstrapMode#DEFAULT}.
* @return the bootstrap mode
*/
protected BootstrapMode getBootstrapMode() {
return BootstrapMode.DEFAULT;
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 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.
@ -18,15 +18,22 @@ package org.springframework.boot.autoconfigure.data.jpa;
import javax.sql.DataSource;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.orm.jpa.EntityManagerFactoryBuilderCustomizer;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.data.jpa.repository.config.JpaRepositoryConfigExtension;
@ -58,7 +65,33 @@ import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;
JpaRepositoryConfigExtension.class })
@ConditionalOnProperty(prefix = "spring.data.jpa.repositories", name = "enabled", havingValue = "true", matchIfMissing = true)
@Import(JpaRepositoriesAutoConfigureRegistrar.class)
@AutoConfigureAfter(HibernateJpaAutoConfiguration.class)
@AutoConfigureAfter({ HibernateJpaAutoConfiguration.class,
TaskExecutionAutoConfiguration.class })
public class JpaRepositoriesAutoConfiguration {
@Bean
@Conditional(BootstrapExecutorCondition.class)
public EntityManagerFactoryBuilderCustomizer entityManagerFactoryBoostrapExecutorCustomizer(
ObjectProvider<AsyncTaskExecutor> taskExecutor) {
return (builder) -> builder.setBootstrapExecutor(taskExecutor.getIfAvailable());
}
private static final class BootstrapExecutorCondition extends AnyNestedCondition {
BootstrapExecutorCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnProperty(prefix = "spring.data.jpa.repositories", name = "bootstrap-mode", havingValue = "deferred", matchIfMissing = false)
static class DeferredBootstrapMode {
}
@ConditionalOnProperty(prefix = "spring.data.jpa.repositories", name = "bootstrap-mode", havingValue = "lazy", matchIfMissing = false)
static class LazyBootstrapMode {
}
}
}

@ -17,12 +17,16 @@
package org.springframework.boot.autoconfigure.data.jpa;
import java.lang.annotation.Annotation;
import java.util.Locale;
import org.springframework.boot.autoconfigure.data.AbstractRepositoryConfigurationSourceSupport;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.data.jpa.repository.config.JpaRepositoryConfigExtension;
import org.springframework.data.repository.config.BootstrapMode;
import org.springframework.data.repository.config.RepositoryConfigurationExtension;
import org.springframework.util.StringUtils;
/**
* {@link ImportBeanDefinitionRegistrar} used to auto-configure Spring Data JPA
@ -34,6 +38,8 @@ import org.springframework.data.repository.config.RepositoryConfigurationExtensi
class JpaRepositoriesAutoConfigureRegistrar
extends AbstractRepositoryConfigurationSourceSupport {
private BootstrapMode bootstrapMode = null;
@Override
protected Class<? extends Annotation> getAnnotation() {
return EnableJpaRepositories.class;
@ -49,6 +55,27 @@ class JpaRepositoriesAutoConfigureRegistrar
return new JpaRepositoryConfigExtension();
}
@Override
protected BootstrapMode getBootstrapMode() {
return (this.bootstrapMode == null) ? super.getBootstrapMode()
: this.bootstrapMode;
}
@Override
public void setEnvironment(Environment environment) {
super.setEnvironment(environment);
configureBootstrapMode(environment);
}
private void configureBootstrapMode(Environment environment) {
String property = environment
.getProperty("spring.data.jpa.repositories.bootstrap-mode");
if (StringUtils.hasText(property)) {
this.bootstrapMode = BootstrapMode
.valueOf(property.toUpperCase(Locale.ENGLISH));
}
}
@EnableJpaRepositories
private static class EnableJpaRepositoriesConfiguration {

@ -0,0 +1,37 @@
/*
* Copyright 2012-2018 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.boot.autoconfigure.orm.jpa;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
/**
* Callback interface that can be used to customize the auto-configured
* {@link EntityManagerFactoryBuilder}.
*
* @author Andy Wilkinson
* @since 2.1.0
*/
@FunctionalInterface
public interface EntityManagerFactoryBuilderCustomizer {
/**
* Customize the given {@code builder}.
* @param builder the builder to customize
*/
void customize(EntityManagerFactoryBuilder builder);
}

@ -16,6 +16,7 @@
package org.springframework.boot.autoconfigure.orm.jpa;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@ -118,11 +119,16 @@ public abstract class JpaBaseConfiguration implements BeanFactoryAware {
@ConditionalOnMissingBean
public EntityManagerFactoryBuilder entityManagerFactoryBuilder(
JpaVendorAdapter jpaVendorAdapter,
ObjectProvider<PersistenceUnitManager> persistenceUnitManager) {
ObjectProvider<PersistenceUnitManager> persistenceUnitManager,
ObjectProvider<List<EntityManagerFactoryBuilderCustomizer>> customizers) {
EntityManagerFactoryBuilder builder = new EntityManagerFactoryBuilder(
jpaVendorAdapter, this.properties.getProperties(),
persistenceUnitManager.getIfAvailable());
builder.setCallback(getVendorCallback());
for (EntityManagerFactoryBuilderCustomizer customizer : customizers
.getIfAvailable(Collections::emptyList)) {
customizer.customize(builder);
}
return builder;
}

@ -156,6 +156,12 @@
"description": "Whether to enable Elasticsearch repositories.",
"defaultValue": true
},
{
"name": "spring.data.jpa.repositories.bootstrap-mode",
"type": "org.springframework.data.repository.config.BootstrapMode",
"description": "Bootstrap mode for JPA repositories.",
"defaultValue": "default"
},
{
"name": "spring.data.jpa.repositories.enabled",
"type": "java.lang.Boolean",

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* Copyright 2012-2018 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.
@ -18,23 +18,25 @@ package org.springframework.boot.autoconfigure.data.jpa;
import javax.persistence.EntityManagerFactory;
import org.junit.After;
import org.junit.Test;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.data.alt.jpa.CityJpaRepository;
import org.springframework.boot.autoconfigure.data.alt.mongo.CityMongoDbRepository;
import org.springframework.boot.autoconfigure.data.alt.solr.CitySolrRepository;
import org.springframework.boot.autoconfigure.data.jpa.city.City;
import org.springframework.boot.autoconfigure.data.jpa.city.CityRepository;
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import static org.assertj.core.api.Assertions.assertThat;
@ -47,47 +49,69 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
public class JpaRepositoriesAutoConfigurationTests {
private AnnotationConfigApplicationContext context;
@After
public void close() {
this.context.close();
}
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(HibernateJpaAutoConfiguration.class,
JpaRepositoriesAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class,
TaskExecutionAutoConfiguration.class))
.withUserConfiguration(EmbeddedDataSourceConfiguration.class);
@Test
public void testDefaultRepositoryConfiguration() {
prepareApplicationContext(TestConfiguration.class);
assertThat(this.context.getBean(CityRepository.class)).isNotNull();
assertThat(this.context.getBean(PlatformTransactionManager.class)).isNotNull();
assertThat(this.context.getBean(EntityManagerFactory.class)).isNotNull();
this.contextRunner.withUserConfiguration(TestConfiguration.class)
.run((context) -> {
assertThat(context).hasSingleBean(CityRepository.class);
assertThat(context).hasSingleBean(PlatformTransactionManager.class);
assertThat(context).hasSingleBean(EntityManagerFactory.class);
assertThat(
context.getBean(LocalContainerEntityManagerFactoryBean.class)
.getBootstrapExecutor()).isNull();
});
}
@Test
public void testOverrideRepositoryConfiguration() {
prepareApplicationContext(CustomConfiguration.class);
assertThat(this.context.getBean(
org.springframework.boot.autoconfigure.data.alt.jpa.CityJpaRepository.class))
.isNotNull();
assertThat(this.context.getBean(PlatformTransactionManager.class)).isNotNull();
assertThat(this.context.getBean(EntityManagerFactory.class)).isNotNull();
this.contextRunner.withUserConfiguration(CustomConfiguration.class)
.run((context) -> {
assertThat(context).hasSingleBean(CityJpaRepository.class);
assertThat(context).hasSingleBean(PlatformTransactionManager.class);
assertThat(context).hasSingleBean(EntityManagerFactory.class);
});
}
@Test(expected = NoSuchBeanDefinitionException.class)
@Test
public void autoConfigurationShouldNotKickInEvenIfManualConfigDidNotCreateAnyRepositories() {
prepareApplicationContext(SortOfInvalidCustomConfiguration.class);
this.contextRunner.withUserConfiguration(SortOfInvalidCustomConfiguration.class)
.run((context) -> assertThat(context)
.doesNotHaveBean(CityRepository.class));
}
this.context.getBean(CityRepository.class);
@Test
public void whenBootstrappingModeIsLazyBoostrapExecutorIsConfigured() {
this.contextRunner.withUserConfiguration(TestConfiguration.class)
.withPropertyValues("spring.data.jpa.repositories.bootstrap-mode=lazy")
.run((context) -> assertThat(
context.getBean(LocalContainerEntityManagerFactoryBean.class)
.getBootstrapExecutor()).isNotNull());
}
private void prepareApplicationContext(Class<?>... configurationClasses) {
this.context = new AnnotationConfigApplicationContext();
this.context.register(configurationClasses);
this.context.register(EmbeddedDataSourceConfiguration.class,
HibernateJpaAutoConfiguration.class,
JpaRepositoriesAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class);
this.context.refresh();
@Test
public void whenBootstrappingModeIsDeferredBoostrapExecutorIsConfigured() {
this.contextRunner.withUserConfiguration(TestConfiguration.class)
.withPropertyValues(
"spring.data.jpa.repositories.bootstrap-mode=deferred")
.run((context) -> assertThat(
context.getBean(LocalContainerEntityManagerFactoryBean.class)
.getBootstrapExecutor()).isNotNull());
}
@Test
public void whenBootstrappingModeIsDefaultBoostrapExecutorIsNotConfigured() {
this.contextRunner.withUserConfiguration(TestConfiguration.class)
.withPropertyValues("spring.data.jpa.repositories.bootstrap-mode=default")
.run((context) -> assertThat(
context.getBean(LocalContainerEntityManagerFactoryBean.class)
.getBootstrapExecutor()).isNull());
}
@Configuration

@ -778,6 +778,7 @@ content into your application. Rather, pick only the properties that you need.
spring.jdbc.template.query-timeout= # Query timeout. Default is to use the JDBC driver's default configuration. If a duration suffix is not specified, seconds will be used.
# JPA ({sc-spring-boot-autoconfigure}/orm/jpa/JpaBaseConfiguration.{sc-ext}[JpaBaseConfiguration], {sc-spring-boot-autoconfigure}/orm/jpa/HibernateJpaAutoConfiguration.{sc-ext}[HibernateJpaAutoConfiguration])
spring.data.jpa.repositories.boostrap-mode=default # Bootstrap mode for JPA repositories.
spring.data.jpa.repositories.enabled=true # Whether to enable JPA repositories.
spring.jpa.database= # Target database to operate on, auto-detected by default. Can be alternatively set using the "databasePlatform" property.
spring.jpa.database-platform= # Name of the target database to operate on, auto-detected by default. Can be alternatively set using the "Database" enum.

@ -3694,6 +3694,12 @@ The following example shows a typical Spring Data repository interface definitio
}
----
Spring Data JPA repositories support three different modes of bootstrapping: default,
deferred, and lazy. To enable deferred or lazy bootstrapping, set the
`spring.data.jpa.repositories.bootstrap-mode` to `deferred` or `lazy` respectively. When
using deferred or lazy bootstrapping, the auto-configured `EntityManagerFactoryBuilder`
will use the context's async task executor, if any, as the bootstrap executor.
TIP: We have barely scratched the surface of Spring Data JPA. For complete details, see
the https://docs.spring.io/spring-data/jpa/docs/current/reference/html/[Spring Data JPA
reference documentation].

@ -25,6 +25,7 @@ import java.util.Set;
import javax.sql.DataSource;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.persistenceunit.PersistenceUnitManager;
@ -55,6 +56,8 @@ public class EntityManagerFactoryBuilder {
private final URL persistenceUnitRootLocation;
private AsyncTaskExecutor bootstrapExecutor;
private EntityManagerFactoryBeanCallback callback;
/**
@ -94,6 +97,16 @@ public class EntityManagerFactoryBuilder {
return new Builder(dataSource);
}
/**
* Configure the bootstrap executor to be used by the
* {@link LocalContainerEntityManagerFactoryBean}.
* @param bootstrapExecutor the executor
* @since 2.1.0
*/
public void setBootstrapExecutor(AsyncTaskExecutor bootstrapExecutor) {
this.bootstrapExecutor = bootstrapExecutor;
}
/**
* An optional callback for new entity manager factory beans.
* @param callback the entity manager factory bean callback
@ -230,6 +243,10 @@ public class EntityManagerFactoryBuilder {
entityManagerFactoryBean
.setPersistenceUnitRootLocation(rootLocation.toString());
}
if (EntityManagerFactoryBuilder.this.bootstrapExecutor != null) {
entityManagerFactoryBean.setBootstrapExecutor(
EntityManagerFactoryBuilder.this.bootstrapExecutor);
}
if (EntityManagerFactoryBuilder.this.callback != null) {
EntityManagerFactoryBuilder.this.callback
.execute(entityManagerFactoryBean);

@ -1,3 +1,4 @@
spring.h2.console.enabled=true
spring.jpa.open-in-view=true
spring.data.jpa.repositories.bootstrap-mode=default
logging.level.org.hibernate.SQL=debug

Loading…
Cancel
Save