Introduce PersistenceManagedTypes and AOT processing of managed entities
This commit adds PersistenceManagedTypes, an abstraction that represents what typically happens at runtime when a persistence unit is built based on classpath scanning. PersistenceManagedTypesScanner extracts the logic that used to be in DefaultPersistenceUnitManager and the latter can be configured with a PersistenceManagedTypes instance. This commits adds a bean registration AOT contribution that retrieves the result of the configured instance and replaces it with the list of managed entities. This has the result of making sure scanning, if any, does not happen at runtime. This also could help if additional hints for managed entities are required. Closes gh-28287
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2002-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.orm.jpa.domain2.entity;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
|
||||
@Entity
|
||||
public class User {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
private Integer id;
|
||||
|
||||
private String firstName;
|
||||
|
||||
private String lastName;
|
||||
|
||||
public Integer getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public String getFirstName() {
|
||||
return this.firstName;
|
||||
}
|
||||
|
||||
public void setFirstName(String firstName) {
|
||||
this.firstName = firstName;
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return this.lastName;
|
||||
}
|
||||
|
||||
public void setLastName(String lastName) {
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Sample package-info for testing purposes.
|
||||
*/
|
||||
@TypeDef(name = "test", typeClass = Object.class)
|
||||
package org.springframework.orm.jpa.domain2;
|
||||
|
||||
import org.hibernate.annotations.TypeDef;
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright 2002-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.orm.jpa.persistenceunit;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aot.test.generator.compile.Compiled;
|
||||
import org.springframework.aot.test.generator.compile.TestCompiler;
|
||||
import org.springframework.context.ApplicationContextInitializer;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.aot.ApplicationContextAotGenerator;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.core.testfixture.aot.generate.TestGenerationContext;
|
||||
import org.springframework.orm.jpa.JpaVendorAdapter;
|
||||
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
||||
import org.springframework.orm.jpa.domain.DriversLicense;
|
||||
import org.springframework.orm.jpa.domain.Person;
|
||||
import org.springframework.orm.jpa.vendor.Database;
|
||||
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link PersistenceManagedTypesBeanRegistrationAotProcessor}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class PersistenceManagedTypesBeanRegistrationAotProcessorTests {
|
||||
|
||||
@Test
|
||||
void processEntityManagerWithPackagesToScan() {
|
||||
GenericApplicationContext context = new AnnotationConfigApplicationContext();
|
||||
context.registerBean(EntityManagerWithPackagesToScanConfiguration.class);
|
||||
compile(context, (initializer, compiled) -> {
|
||||
GenericApplicationContext freshApplicationContext = toFreshApplicationContext(
|
||||
initializer);
|
||||
PersistenceManagedTypes persistenceManagedTypes = freshApplicationContext.getBean(
|
||||
"persistenceManagedTypes", PersistenceManagedTypes.class);
|
||||
assertThat(persistenceManagedTypes.getManagedClassNames()).containsExactlyInAnyOrder(
|
||||
DriversLicense.class.getName(), Person.class.getName());
|
||||
assertThat(persistenceManagedTypes.getManagedPackages()).isEmpty();
|
||||
assertThat(freshApplicationContext.getBean(
|
||||
EntityManagerWithPackagesToScanConfiguration.class).scanningInvoked).isFalse();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void compile(GenericApplicationContext applicationContext,
|
||||
BiConsumer<ApplicationContextInitializer<GenericApplicationContext>, Compiled> result) {
|
||||
ApplicationContextAotGenerator generator = new ApplicationContextAotGenerator();
|
||||
TestGenerationContext generationContext = new TestGenerationContext();
|
||||
generator.processAheadOfTime(applicationContext, generationContext);
|
||||
generationContext.writeGeneratedContent();
|
||||
TestCompiler.forSystem().withFiles(generationContext.getGeneratedFiles()).compile(compiled ->
|
||||
result.accept(compiled.getInstance(ApplicationContextInitializer.class), compiled));
|
||||
}
|
||||
|
||||
private GenericApplicationContext toFreshApplicationContext(
|
||||
ApplicationContextInitializer<GenericApplicationContext> initializer) {
|
||||
GenericApplicationContext freshApplicationContext = new GenericApplicationContext();
|
||||
initializer.initialize(freshApplicationContext);
|
||||
freshApplicationContext.refresh();
|
||||
return freshApplicationContext;
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public static class EntityManagerWithPackagesToScanConfiguration {
|
||||
|
||||
private boolean scanningInvoked;
|
||||
|
||||
@Bean
|
||||
public DataSource mockDataSource() {
|
||||
return mock(DataSource.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public HibernateJpaVendorAdapter jpaVendorAdapter() {
|
||||
HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
|
||||
jpaVendorAdapter.setDatabase(Database.HSQL);
|
||||
return jpaVendorAdapter;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PersistenceManagedTypes persistenceManagedTypes(ResourceLoader resourceLoader) {
|
||||
this.scanningInvoked = true;
|
||||
return new PersistenceManagedTypesScanner(resourceLoader)
|
||||
.scan("org.springframework.orm.jpa.domain");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource,
|
||||
JpaVendorAdapter jpaVendorAdapter, PersistenceManagedTypes persistenceManagedTypes) {
|
||||
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
|
||||
entityManagerFactoryBean.setDataSource(dataSource);
|
||||
entityManagerFactoryBean.setJpaVendorAdapter(jpaVendorAdapter);
|
||||
entityManagerFactoryBean.setManagedTypes(persistenceManagedTypes);
|
||||
return entityManagerFactoryBean;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2002-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.orm.jpa.persistenceunit;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.context.testfixture.index.CandidateComponentsTestClassLoader;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
import org.springframework.orm.jpa.domain.DriversLicense;
|
||||
import org.springframework.orm.jpa.domain.Person;
|
||||
import org.springframework.orm.jpa.domain2.entity.User;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link PersistenceManagedTypesScanner}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class PersistenceManagedTypesScannerTests {
|
||||
|
||||
private final PersistenceManagedTypesScanner scanner = new PersistenceManagedTypesScanner(new DefaultResourceLoader());
|
||||
|
||||
@Test
|
||||
void scanPackageWithOnlyEntities() {
|
||||
PersistenceManagedTypes managedTypes = this.scanner.scan("org.springframework.orm.jpa.domain");
|
||||
assertThat(managedTypes.getManagedClassNames()).containsExactlyInAnyOrder(
|
||||
Person.class.getName(), DriversLicense.class.getName());
|
||||
assertThat(managedTypes.getManagedPackages()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void scanPackageWithEntitiesAndManagedPackages() {
|
||||
PersistenceManagedTypes managedTypes = this.scanner.scan("org.springframework.orm.jpa.domain2");
|
||||
assertThat(managedTypes.getManagedClassNames()).containsExactlyInAnyOrder(User.class.getName());
|
||||
assertThat(managedTypes.getManagedPackages()).containsExactlyInAnyOrder(
|
||||
"org.springframework.orm.jpa.domain2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void scanPackageUsesIndexIfPresent() {
|
||||
DefaultResourceLoader resourceLoader = new DefaultResourceLoader(
|
||||
CandidateComponentsTestClassLoader.index(getClass().getClassLoader(),
|
||||
new ClassPathResource("test-spring.components", getClass())));
|
||||
PersistenceManagedTypes managedTypes = new PersistenceManagedTypesScanner(resourceLoader).scan("com.example");
|
||||
assertThat(managedTypes.getManagedClassNames()).containsExactlyInAnyOrder(
|
||||
"com.example.domain.Person", "com.example.domain.Address");
|
||||
assertThat(managedTypes.getManagedPackages()).containsExactlyInAnyOrder(
|
||||
"com.example.domain");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2002-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.orm.jpa.persistenceunit;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Tests for {@link PersistenceManagedTypes}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class PersistenceManagedTypesTests {
|
||||
|
||||
@Test
|
||||
void createWithManagedClassNames() {
|
||||
PersistenceManagedTypes managedTypes = PersistenceManagedTypes.of(
|
||||
"com.example.One", "com.example.Two");
|
||||
assertThat(managedTypes.getManagedClassNames()).containsExactly(
|
||||
"com.example.One", "com.example.Two");
|
||||
assertThat(managedTypes.getManagedPackages()).isEmpty();
|
||||
assertThat(managedTypes.getPersistenceUnitRootUrl()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWithNullManagedClasses() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> PersistenceManagedTypes.of(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWithManagedClassNamesAndPackages() {
|
||||
PersistenceManagedTypes managedTypes = PersistenceManagedTypes.of(
|
||||
List.of("com.example.One", "com.example.Two"), List.of("com.example"));
|
||||
assertThat(managedTypes.getManagedClassNames()).containsExactly(
|
||||
"com.example.One", "com.example.Two");
|
||||
assertThat(managedTypes.getManagedPackages()).containsExactly("com.example");
|
||||
assertThat(managedTypes.getPersistenceUnitRootUrl()).isNull();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
com.example.domain.Person=jakarta.persistence.Entity
|
||||
com.example.domain.Address=jakarta.persistence.Entity
|
||||
|
||||
com.example.domain=package-info
|
||||
Reference in New Issue
Block a user