Skip to content

Commit e3ddb54

Browse files
committed
Adapt JPA auto-configuration to PersistenceManagedTypes
This commit exposes a PersistenceManagedTypes bean with the entities to consider in a typical auto-configuration scenario. This allows the result of the scanning to be optimized AOT, if necessary. Closes spring-projectsgh-32119
1 parent f2f5bae commit e3ddb54

File tree

4 files changed

+135
-22
lines changed

4 files changed

+135
-22
lines changed

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

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,8 @@
2525
import org.apache.commons.logging.Log;
2626
import org.apache.commons.logging.LogFactory;
2727

28-
import org.springframework.beans.BeansException;
2928
import org.springframework.beans.factory.BeanFactory;
30-
import org.springframework.beans.factory.BeanFactoryAware;
3129
import org.springframework.beans.factory.ObjectProvider;
32-
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
3330
import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
3431
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
3532
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@@ -45,9 +42,12 @@
4542
import org.springframework.context.annotation.Bean;
4643
import org.springframework.context.annotation.Configuration;
4744
import org.springframework.context.annotation.Primary;
45+
import org.springframework.core.io.ResourceLoader;
4846
import org.springframework.orm.jpa.JpaTransactionManager;
4947
import org.springframework.orm.jpa.JpaVendorAdapter;
5048
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
49+
import org.springframework.orm.jpa.persistenceunit.PersistenceManagedTypes;
50+
import org.springframework.orm.jpa.persistenceunit.PersistenceManagedTypesScanner;
5151
import org.springframework.orm.jpa.persistenceunit.PersistenceUnitManager;
5252
import org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter;
5353
import org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor;
@@ -73,16 +73,14 @@
7373
*/
7474
@Configuration(proxyBeanMethods = false)
7575
@EnableConfigurationProperties(JpaProperties.class)
76-
public abstract class JpaBaseConfiguration implements BeanFactoryAware {
76+
public abstract class JpaBaseConfiguration {
7777

7878
private final DataSource dataSource;
7979

8080
private final JpaProperties properties;
8181

8282
private final JtaTransactionManager jtaTransactionManager;
8383

84-
private ConfigurableListableBeanFactory beanFactory;
85-
8684
protected JpaBaseConfiguration(DataSource dataSource, JpaProperties properties,
8785
ObjectProvider<JtaTransactionManager> jtaTransactionManager) {
8886
this.dataSource = dataSource;
@@ -128,11 +126,12 @@ public EntityManagerFactoryBuilder entityManagerFactoryBuilder(JpaVendorAdapter
128126
@Bean
129127
@Primary
130128
@ConditionalOnMissingBean({ LocalContainerEntityManagerFactoryBean.class, EntityManagerFactory.class })
131-
public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder factoryBuilder) {
129+
public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder factoryBuilder,
130+
PersistenceManagedTypes persistenceManagedTypes) {
132131
Map<String, Object> vendorProperties = getVendorProperties();
133132
customizeVendorProperties(vendorProperties);
134-
return factoryBuilder.dataSource(this.dataSource).packages(getPackagesToScan()).properties(vendorProperties)
135-
.mappingResources(getMappingResources()).jta(isJta()).build();
133+
return factoryBuilder.dataSource(this.dataSource).managedTypes(persistenceManagedTypes)
134+
.properties(vendorProperties).mappingResources(getMappingResources()).jta(isJta()).build();
136135
}
137136

138137
protected abstract AbstractJpaVendorAdapter createJpaVendorAdapter();
@@ -147,14 +146,6 @@ public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManager
147146
protected void customizeVendorProperties(Map<String, Object> vendorProperties) {
148147
}
149148

150-
protected String[] getPackagesToScan() {
151-
List<String> packages = EntityScanPackages.get(this.beanFactory).getPackageNames();
152-
if (packages.isEmpty() && AutoConfigurationPackages.has(this.beanFactory)) {
153-
packages = AutoConfigurationPackages.get(this.beanFactory);
154-
}
155-
return StringUtils.toStringArray(packages);
156-
}
157-
158149
private String[] getMappingResources() {
159150
List<String> mappingResources = this.properties.getMappingResources();
160151
return (!ObjectUtils.isEmpty(mappingResources) ? StringUtils.toStringArray(mappingResources) : null);
@@ -192,9 +183,26 @@ protected final DataSource getDataSource() {
192183
return this.dataSource;
193184
}
194185

195-
@Override
196-
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
197-
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
186+
@Configuration(proxyBeanMethods = false)
187+
@ConditionalOnMissingBean({ LocalContainerEntityManagerFactoryBean.class, EntityManagerFactory.class })
188+
static class PersistenceManagedTypesConfiguration {
189+
190+
@Bean
191+
@Primary
192+
@ConditionalOnMissingBean
193+
PersistenceManagedTypes persistenceManagedTypes(BeanFactory beanFactory, ResourceLoader resourceLoader) {
194+
String[] packagesToScan = getPackagesToScan(beanFactory);
195+
return new PersistenceManagedTypesScanner(resourceLoader).scan(packagesToScan);
196+
}
197+
198+
private static String[] getPackagesToScan(BeanFactory beanFactory) {
199+
List<String> packages = EntityScanPackages.get(beanFactory).getPackageNames();
200+
if (packages.isEmpty() && AutoConfigurationPackages.has(beanFactory)) {
201+
packages = AutoConfigurationPackages.get(beanFactory);
202+
}
203+
return StringUtils.toStringArray(packages);
204+
}
205+
198206
}
199207

200208
@Configuration(proxyBeanMethods = false)

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/AbstractJpaAutoConfigurationTests.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,16 @@
2323

2424
import javax.sql.DataSource;
2525

26+
import jakarta.persistence.EntityManager;
2627
import jakarta.persistence.EntityManagerFactory;
28+
import jakarta.persistence.metamodel.ManagedType;
2729
import jakarta.persistence.spi.PersistenceUnitInfo;
2830
import org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform;
2931
import org.junit.jupiter.api.Test;
3032

3133
import org.springframework.boot.autoconfigure.AutoConfigurations;
3234
import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage;
35+
import org.springframework.boot.autoconfigure.data.jpa.country.Country;
3336
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
3437
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
3538
import org.springframework.boot.autoconfigure.orm.jpa.test.City;
@@ -49,6 +52,7 @@
4952
import org.springframework.orm.jpa.JpaVendorAdapter;
5053
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
5154
import org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager;
55+
import org.springframework.orm.jpa.persistenceunit.PersistenceManagedTypes;
5256
import org.springframework.orm.jpa.persistenceunit.PersistenceUnitManager;
5357
import org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter;
5458
import org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor;
@@ -112,6 +116,7 @@ void configuredWithAutoConfiguredDataSource() {
112116
assertThat(context).hasSingleBean(DataSource.class);
113117
assertThat(context).hasSingleBean(JpaTransactionManager.class);
114118
assertThat(context).hasSingleBean(EntityManagerFactory.class);
119+
assertThat(context).hasSingleBean(PersistenceManagedTypes.class);
115120
});
116121
}
117122

@@ -121,6 +126,7 @@ void configuredWithSingleCandidateDataSource() {
121126
assertThat(context).getBeans(DataSource.class).hasSize(2);
122127
assertThat(context).hasSingleBean(JpaTransactionManager.class);
123128
assertThat(context).hasSingleBean(EntityManagerFactory.class);
129+
assertThat(context).hasSingleBean(PersistenceManagedTypes.class);
124130
});
125131
}
126132

@@ -225,6 +231,28 @@ void usesManuallyDefinedTransactionManagerBeanIfAvailable() {
225231
});
226232
}
227233

234+
@Test
235+
void defaultPersistenceManagedTypes() {
236+
this.contextRunner.run((context) -> {
237+
assertThat(context).hasSingleBean(PersistenceManagedTypes.class);
238+
EntityManager entityManager = context.getBean(EntityManagerFactory.class).createEntityManager();
239+
assertThat(entityManager.getMetamodel().getManagedTypes().stream().map(ManagedType::getJavaType)
240+
.toArray(Class<?>[]::new)).contains(City.class).doesNotContain(Country.class);
241+
});
242+
}
243+
244+
@Test
245+
void customPersistenceManagedTypes() {
246+
this.contextRunner
247+
.withBean(PersistenceManagedTypes.class, () -> PersistenceManagedTypes.of(Country.class.getName()))
248+
.run((context) -> {
249+
assertThat(context).hasSingleBean(PersistenceManagedTypes.class);
250+
EntityManager entityManager = context.getBean(EntityManagerFactory.class).createEntityManager();
251+
assertThat(entityManager.getMetamodel().getManagedTypes().stream().map(ManagedType::getJavaType)
252+
.toArray(Class<?>[]::new)).contains(Country.class).doesNotContain(City.class);
253+
});
254+
}
255+
228256
@Test
229257
void customPersistenceUnitManager() {
230258
this.contextRunner.withUserConfiguration(TestConfigurationWithCustomPersistenceUnitManager.class)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright 2012-2022 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+
* https://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.domain.country;
18+
19+
import java.io.Serializable;
20+
21+
import jakarta.persistence.Column;
22+
import jakarta.persistence.Entity;
23+
import jakarta.persistence.GeneratedValue;
24+
import jakarta.persistence.Id;
25+
import org.hibernate.envers.Audited;
26+
27+
@Entity
28+
public class Country implements Serializable {
29+
30+
private static final long serialVersionUID = 1L;
31+
32+
@Id
33+
@GeneratedValue
34+
private Long id;
35+
36+
@Audited
37+
@Column
38+
private String name;
39+
40+
public Long getId() {
41+
return this.id;
42+
}
43+
44+
public void setId(Long id) {
45+
this.id = id;
46+
}
47+
48+
public String getName() {
49+
return this.name;
50+
}
51+
52+
public void setName(String name) {
53+
this.name = name;
54+
}
55+
56+
}

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

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2022 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.
@@ -28,6 +28,7 @@
2828
import org.springframework.core.task.AsyncTaskExecutor;
2929
import org.springframework.orm.jpa.JpaVendorAdapter;
3030
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
31+
import org.springframework.orm.jpa.persistenceunit.PersistenceManagedTypes;
3132
import org.springframework.orm.jpa.persistenceunit.PersistenceUnitManager;
3233
import org.springframework.orm.jpa.persistenceunit.PersistenceUnitPostProcessor;
3334
import org.springframework.util.ClassUtils;
@@ -125,6 +126,8 @@ public final class Builder {
125126

126127
private DataSource dataSource;
127128

129+
private PersistenceManagedTypes managedTypes;
130+
128131
private String[] packagesToScan;
129132

130133
private String persistenceUnit;
@@ -139,10 +142,22 @@ private Builder(DataSource dataSource) {
139142
this.dataSource = dataSource;
140143
}
141144

145+
/**
146+
* The persistence managed types, providing both the managed entities and packages
147+
* the entity manager should consider.
148+
* @param managedTypes managed types.
149+
* @return the builder for fluent usage
150+
*/
151+
public Builder managedTypes(PersistenceManagedTypes managedTypes) {
152+
this.managedTypes = managedTypes;
153+
return this;
154+
}
155+
142156
/**
143157
* The names of packages to scan for {@code @Entity} annotations.
144158
* @param packagesToScan packages to scan
145159
* @return the builder for fluent usage
160+
* @see #managedTypes(PersistenceManagedTypes)
146161
*/
147162
public Builder packages(String... packagesToScan) {
148163
this.packagesToScan = packagesToScan;
@@ -153,6 +168,7 @@ public Builder packages(String... packagesToScan) {
153168
* The classes whose packages should be scanned for {@code @Entity} annotations.
154169
* @param basePackageClasses the classes to use
155170
* @return the builder for fluent usage
171+
* @see #managedTypes(PersistenceManagedTypes)
156172
*/
157173
public Builder packages(Class<?>... basePackageClasses) {
158174
Set<String> packages = new HashSet<>();
@@ -233,7 +249,12 @@ public LocalContainerEntityManagerFactoryBean build() {
233249
else {
234250
entityManagerFactoryBean.setDataSource(this.dataSource);
235251
}
236-
entityManagerFactoryBean.setPackagesToScan(this.packagesToScan);
252+
if (this.managedTypes != null) {
253+
entityManagerFactoryBean.setManagedTypes(this.managedTypes);
254+
}
255+
else {
256+
entityManagerFactoryBean.setPackagesToScan(this.packagesToScan);
257+
}
237258
entityManagerFactoryBean.getJpaPropertyMap().putAll(EntityManagerFactoryBuilder.this.jpaProperties);
238259
entityManagerFactoryBean.getJpaPropertyMap().putAll(this.properties);
239260
if (!ObjectUtils.isEmpty(this.mappingResources)) {

0 commit comments

Comments
 (0)