Skip to content

Commit f28528a

Browse files
committed
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
1 parent ab1f593 commit f28528a

File tree

11 files changed

+210
-35
lines changed

11 files changed

+210
-35
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/AbstractRepositoryConfigurationSourceSupport.java

+17
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.springframework.core.type.AnnotationMetadata;
3232
import org.springframework.core.type.StandardAnnotationMetadata;
3333
import org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource;
34+
import org.springframework.data.repository.config.BootstrapMode;
3435
import org.springframework.data.repository.config.RepositoryConfigurationDelegate;
3536
import org.springframework.data.repository.config.RepositoryConfigurationExtension;
3637
import org.springframework.data.util.Streamable;
@@ -72,6 +73,13 @@ public Streamable<String> getBasePackages() {
7273
return AbstractRepositoryConfigurationSourceSupport.this
7374
.getBasePackages();
7475
}
76+
77+
@Override
78+
public BootstrapMode getBootstrapMode() {
79+
return AbstractRepositoryConfigurationSourceSupport.this
80+
.getBootstrapMode();
81+
}
82+
7583
};
7684
}
7785

@@ -97,6 +105,15 @@ protected Streamable<String> getBasePackages() {
97105
*/
98106
protected abstract RepositoryConfigurationExtension getRepositoryConfigurationExtension();
99107

108+
/**
109+
* The {@link BootstrapMode} for the particular repository support. Defaults to
110+
* {@link BootstrapMode#DEFAULT}.
111+
* @return the bootstrap mode
112+
*/
113+
protected BootstrapMode getBootstrapMode() {
114+
return BootstrapMode.DEFAULT;
115+
}
116+
100117
@Override
101118
public void setResourceLoader(ResourceLoader resourceLoader) {
102119
this.resourceLoader = resourceLoader;

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/jpa/JpaRepositoriesAutoConfiguration.java

+35-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2017 the original author or authors.
2+
* Copyright 2012-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,15 +18,22 @@
1818

1919
import javax.sql.DataSource;
2020

21+
import org.springframework.beans.factory.ObjectProvider;
2122
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
2223
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
24+
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
2325
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
2426
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
2527
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
2628
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
29+
import org.springframework.boot.autoconfigure.orm.jpa.EntityManagerFactoryBuilderCustomizer;
2730
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
31+
import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
32+
import org.springframework.context.annotation.Bean;
33+
import org.springframework.context.annotation.Conditional;
2834
import org.springframework.context.annotation.Configuration;
2935
import org.springframework.context.annotation.Import;
36+
import org.springframework.core.task.AsyncTaskExecutor;
3037
import org.springframework.data.jpa.repository.JpaRepository;
3138
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
3239
import org.springframework.data.jpa.repository.config.JpaRepositoryConfigExtension;
@@ -58,7 +65,33 @@
5865
JpaRepositoryConfigExtension.class })
5966
@ConditionalOnProperty(prefix = "spring.data.jpa.repositories", name = "enabled", havingValue = "true", matchIfMissing = true)
6067
@Import(JpaRepositoriesAutoConfigureRegistrar.class)
61-
@AutoConfigureAfter(HibernateJpaAutoConfiguration.class)
68+
@AutoConfigureAfter({ HibernateJpaAutoConfiguration.class,
69+
TaskExecutionAutoConfiguration.class })
6270
public class JpaRepositoriesAutoConfiguration {
6371

72+
@Bean
73+
@Conditional(BootstrapExecutorCondition.class)
74+
public EntityManagerFactoryBuilderCustomizer entityManagerFactoryBoostrapExecutorCustomizer(
75+
ObjectProvider<AsyncTaskExecutor> taskExecutor) {
76+
return (builder) -> builder.setBootstrapExecutor(taskExecutor.getIfAvailable());
77+
}
78+
79+
private static final class BootstrapExecutorCondition extends AnyNestedCondition {
80+
81+
BootstrapExecutorCondition() {
82+
super(ConfigurationPhase.REGISTER_BEAN);
83+
}
84+
85+
@ConditionalOnProperty(prefix = "spring.data.jpa.repositories", name = "bootstrap-mode", havingValue = "deferred", matchIfMissing = false)
86+
static class DeferredBootstrapMode {
87+
88+
}
89+
90+
@ConditionalOnProperty(prefix = "spring.data.jpa.repositories", name = "bootstrap-mode", havingValue = "lazy", matchIfMissing = false)
91+
static class LazyBootstrapMode {
92+
93+
}
94+
95+
}
96+
6497
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/jpa/JpaRepositoriesAutoConfigureRegistrar.java

+27
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,16 @@
1717
package org.springframework.boot.autoconfigure.data.jpa;
1818

1919
import java.lang.annotation.Annotation;
20+
import java.util.Locale;
2021

2122
import org.springframework.boot.autoconfigure.data.AbstractRepositoryConfigurationSourceSupport;
2223
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
24+
import org.springframework.core.env.Environment;
2325
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
2426
import org.springframework.data.jpa.repository.config.JpaRepositoryConfigExtension;
27+
import org.springframework.data.repository.config.BootstrapMode;
2528
import org.springframework.data.repository.config.RepositoryConfigurationExtension;
29+
import org.springframework.util.StringUtils;
2630

2731
/**
2832
* {@link ImportBeanDefinitionRegistrar} used to auto-configure Spring Data JPA
@@ -34,6 +38,8 @@
3438
class JpaRepositoriesAutoConfigureRegistrar
3539
extends AbstractRepositoryConfigurationSourceSupport {
3640

41+
private BootstrapMode bootstrapMode = null;
42+
3743
@Override
3844
protected Class<? extends Annotation> getAnnotation() {
3945
return EnableJpaRepositories.class;
@@ -49,6 +55,27 @@ protected RepositoryConfigurationExtension getRepositoryConfigurationExtension()
4955
return new JpaRepositoryConfigExtension();
5056
}
5157

58+
@Override
59+
protected BootstrapMode getBootstrapMode() {
60+
return (this.bootstrapMode == null) ? super.getBootstrapMode()
61+
: this.bootstrapMode;
62+
}
63+
64+
@Override
65+
public void setEnvironment(Environment environment) {
66+
super.setEnvironment(environment);
67+
configureBootstrapMode(environment);
68+
}
69+
70+
private void configureBootstrapMode(Environment environment) {
71+
String property = environment
72+
.getProperty("spring.data.jpa.repositories.bootstrap-mode");
73+
if (StringUtils.hasText(property)) {
74+
this.bootstrapMode = BootstrapMode
75+
.valueOf(property.toUpperCase(Locale.ENGLISH));
76+
}
77+
}
78+
5279
@EnableJpaRepositories
5380
private static class EnableJpaRepositoriesConfiguration {
5481

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright 2012-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.orm.jpa;
18+
19+
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
20+
21+
/**
22+
* Callback interface that can be used to customize the auto-configured
23+
* {@link EntityManagerFactoryBuilder}.
24+
*
25+
* @author Andy Wilkinson
26+
* @since 2.1.0
27+
*/
28+
@FunctionalInterface
29+
public interface EntityManagerFactoryBuilderCustomizer {
30+
31+
/**
32+
* Customize the given {@code builder}.
33+
* @param builder the builder to customize
34+
*/
35+
void customize(EntityManagerFactoryBuilder builder);
36+
37+
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/JpaBaseConfiguration.java

+7-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.boot.autoconfigure.orm.jpa;
1818

19+
import java.util.Collections;
1920
import java.util.List;
2021
import java.util.Map;
2122

@@ -118,11 +119,16 @@ public JpaVendorAdapter jpaVendorAdapter() {
118119
@ConditionalOnMissingBean
119120
public EntityManagerFactoryBuilder entityManagerFactoryBuilder(
120121
JpaVendorAdapter jpaVendorAdapter,
121-
ObjectProvider<PersistenceUnitManager> persistenceUnitManager) {
122+
ObjectProvider<PersistenceUnitManager> persistenceUnitManager,
123+
ObjectProvider<List<EntityManagerFactoryBuilderCustomizer>> customizers) {
122124
EntityManagerFactoryBuilder builder = new EntityManagerFactoryBuilder(
123125
jpaVendorAdapter, this.properties.getProperties(),
124126
persistenceUnitManager.getIfAvailable());
125127
builder.setCallback(getVendorCallback());
128+
for (EntityManagerFactoryBuilderCustomizer customizer : customizers
129+
.getIfAvailable(Collections::emptyList)) {
130+
customizer.customize(builder);
131+
}
126132
return builder;
127133
}
128134

spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json

+6
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,12 @@
156156
"description": "Whether to enable Elasticsearch repositories.",
157157
"defaultValue": true
158158
},
159+
{
160+
"name": "spring.data.jpa.repositories.bootstrap-mode",
161+
"type": "org.springframework.data.repository.config.BootstrapMode",
162+
"description": "Bootstrap mode for JPA repositories.",
163+
"defaultValue": "default"
164+
},
159165
{
160166
"name": "spring.data.jpa.repositories.enabled",
161167
"type": "java.lang.Boolean",

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/jpa/JpaRepositoriesAutoConfigurationTests.java

+56-32
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2017 the original author or authors.
2+
* Copyright 2012-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,23 +18,25 @@
1818

1919
import javax.persistence.EntityManagerFactory;
2020

21-
import org.junit.After;
2221
import org.junit.Test;
2322

24-
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
23+
import org.springframework.boot.autoconfigure.AutoConfigurations;
2524
import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage;
2625
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
26+
import org.springframework.boot.autoconfigure.data.alt.jpa.CityJpaRepository;
2727
import org.springframework.boot.autoconfigure.data.alt.mongo.CityMongoDbRepository;
2828
import org.springframework.boot.autoconfigure.data.alt.solr.CitySolrRepository;
2929
import org.springframework.boot.autoconfigure.data.jpa.city.City;
3030
import org.springframework.boot.autoconfigure.data.jpa.city.CityRepository;
3131
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration;
3232
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
33-
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
33+
import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
34+
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
3435
import org.springframework.context.annotation.ComponentScan.Filter;
3536
import org.springframework.context.annotation.Configuration;
3637
import org.springframework.context.annotation.FilterType;
3738
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
39+
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
3840
import org.springframework.transaction.PlatformTransactionManager;
3941

4042
import static org.assertj.core.api.Assertions.assertThat;
@@ -47,47 +49,69 @@
4749
*/
4850
public class JpaRepositoriesAutoConfigurationTests {
4951

50-
private AnnotationConfigApplicationContext context;
51-
52-
@After
53-
public void close() {
54-
this.context.close();
55-
}
52+
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
53+
.withConfiguration(AutoConfigurations.of(HibernateJpaAutoConfiguration.class,
54+
JpaRepositoriesAutoConfiguration.class,
55+
PropertyPlaceholderAutoConfiguration.class,
56+
TaskExecutionAutoConfiguration.class))
57+
.withUserConfiguration(EmbeddedDataSourceConfiguration.class);
5658

5759
@Test
5860
public void testDefaultRepositoryConfiguration() {
59-
prepareApplicationContext(TestConfiguration.class);
60-
61-
assertThat(this.context.getBean(CityRepository.class)).isNotNull();
62-
assertThat(this.context.getBean(PlatformTransactionManager.class)).isNotNull();
63-
assertThat(this.context.getBean(EntityManagerFactory.class)).isNotNull();
61+
this.contextRunner.withUserConfiguration(TestConfiguration.class)
62+
.run((context) -> {
63+
assertThat(context).hasSingleBean(CityRepository.class);
64+
assertThat(context).hasSingleBean(PlatformTransactionManager.class);
65+
assertThat(context).hasSingleBean(EntityManagerFactory.class);
66+
assertThat(
67+
context.getBean(LocalContainerEntityManagerFactoryBean.class)
68+
.getBootstrapExecutor()).isNull();
69+
});
6470
}
6571

6672
@Test
6773
public void testOverrideRepositoryConfiguration() {
68-
prepareApplicationContext(CustomConfiguration.class);
69-
assertThat(this.context.getBean(
70-
org.springframework.boot.autoconfigure.data.alt.jpa.CityJpaRepository.class))
71-
.isNotNull();
72-
assertThat(this.context.getBean(PlatformTransactionManager.class)).isNotNull();
73-
assertThat(this.context.getBean(EntityManagerFactory.class)).isNotNull();
74+
this.contextRunner.withUserConfiguration(CustomConfiguration.class)
75+
.run((context) -> {
76+
assertThat(context).hasSingleBean(CityJpaRepository.class);
77+
assertThat(context).hasSingleBean(PlatformTransactionManager.class);
78+
assertThat(context).hasSingleBean(EntityManagerFactory.class);
79+
});
7480
}
7581

76-
@Test(expected = NoSuchBeanDefinitionException.class)
82+
@Test
7783
public void autoConfigurationShouldNotKickInEvenIfManualConfigDidNotCreateAnyRepositories() {
78-
prepareApplicationContext(SortOfInvalidCustomConfiguration.class);
84+
this.contextRunner.withUserConfiguration(SortOfInvalidCustomConfiguration.class)
85+
.run((context) -> assertThat(context)
86+
.doesNotHaveBean(CityRepository.class));
87+
}
7988

80-
this.context.getBean(CityRepository.class);
89+
@Test
90+
public void whenBootstrappingModeIsLazyBoostrapExecutorIsConfigured() {
91+
this.contextRunner.withUserConfiguration(TestConfiguration.class)
92+
.withPropertyValues("spring.data.jpa.repositories.bootstrap-mode=lazy")
93+
.run((context) -> assertThat(
94+
context.getBean(LocalContainerEntityManagerFactoryBean.class)
95+
.getBootstrapExecutor()).isNotNull());
8196
}
8297

83-
private void prepareApplicationContext(Class<?>... configurationClasses) {
84-
this.context = new AnnotationConfigApplicationContext();
85-
this.context.register(configurationClasses);
86-
this.context.register(EmbeddedDataSourceConfiguration.class,
87-
HibernateJpaAutoConfiguration.class,
88-
JpaRepositoriesAutoConfiguration.class,
89-
PropertyPlaceholderAutoConfiguration.class);
90-
this.context.refresh();
98+
@Test
99+
public void whenBootstrappingModeIsDeferredBoostrapExecutorIsConfigured() {
100+
this.contextRunner.withUserConfiguration(TestConfiguration.class)
101+
.withPropertyValues(
102+
"spring.data.jpa.repositories.bootstrap-mode=deferred")
103+
.run((context) -> assertThat(
104+
context.getBean(LocalContainerEntityManagerFactoryBean.class)
105+
.getBootstrapExecutor()).isNotNull());
106+
}
107+
108+
@Test
109+
public void whenBootstrappingModeIsDefaultBoostrapExecutorIsNotConfigured() {
110+
this.contextRunner.withUserConfiguration(TestConfiguration.class)
111+
.withPropertyValues("spring.data.jpa.repositories.bootstrap-mode=default")
112+
.run((context) -> assertThat(
113+
context.getBean(LocalContainerEntityManagerFactoryBean.class)
114+
.getBootstrapExecutor()).isNull());
91115
}
92116

93117
@Configuration

spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc

+1
Original file line numberDiff line numberDiff line change
@@ -778,6 +778,7 @@ content into your application. Rather, pick only the properties that you need.
778778
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.
779779
780780
# JPA ({sc-spring-boot-autoconfigure}/orm/jpa/JpaBaseConfiguration.{sc-ext}[JpaBaseConfiguration], {sc-spring-boot-autoconfigure}/orm/jpa/HibernateJpaAutoConfiguration.{sc-ext}[HibernateJpaAutoConfiguration])
781+
spring.data.jpa.repositories.boostrap-mode=default # Bootstrap mode for JPA repositories.
781782
spring.data.jpa.repositories.enabled=true # Whether to enable JPA repositories.
782783
spring.jpa.database= # Target database to operate on, auto-detected by default. Can be alternatively set using the "databasePlatform" property.
783784
spring.jpa.database-platform= # Name of the target database to operate on, auto-detected by default. Can be alternatively set using the "Database" enum.

spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc

+6
Original file line numberDiff line numberDiff line change
@@ -3694,6 +3694,12 @@ The following example shows a typical Spring Data repository interface definitio
36943694
}
36953695
----
36963696

3697+
Spring Data JPA repositories support three different modes of bootstrapping: default,
3698+
deferred, and lazy. To enable deferred or lazy bootstrapping, set the
3699+
`spring.data.jpa.repositories.bootstrap-mode` to `deferred` or `lazy` respectively. When
3700+
using deferred or lazy bootstrapping, the auto-configured `EntityManagerFactoryBuilder`
3701+
will use the context's async task executor, if any, as the bootstrap executor.
3702+
36973703
TIP: We have barely scratched the surface of Spring Data JPA. For complete details, see
36983704
the https://docs.spring.io/spring-data/jpa/docs/current/reference/html/[Spring Data JPA
36993705
reference documentation].

0 commit comments

Comments
 (0)