Skip to content

Commit 4118196

Browse files
committed
Avoid repeated creation of EntityManagerFactory in AOT repository generation.
We use now a cached variant by deferring EntityManagerFactory creation.
1 parent a184af4 commit 4118196

File tree

4 files changed

+226
-38
lines changed

4 files changed

+226
-38
lines changed

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/aot/AotMetamodel.java

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
import org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl;
3838
import org.hibernate.jpa.boot.internal.PersistenceUnitInfoDescriptor;
3939
import org.jspecify.annotations.Nullable;
40-
import org.springframework.data.repository.config.AotRepositoryContext;
40+
4141
import org.springframework.data.util.Lazy;
4242
import org.springframework.orm.jpa.persistenceunit.PersistenceManagedTypes;
4343
import org.springframework.orm.jpa.persistenceunit.SpringPersistenceUnitInfo;
@@ -55,19 +55,6 @@ class AotMetamodel implements Metamodel {
5555
private final Lazy<EntityManagerFactory> entityManagerFactory;
5656
private final Lazy<EntityManager> entityManager = Lazy.of(() -> getEntityManagerFactory().createEntityManager());
5757

58-
public AotMetamodel(AotRepositoryContext repositoryContext) {
59-
this(repositoryContext.getResolvedTypes().stream().filter(AotMetamodel::isJakartaAnnotated).map(Class::getName)
60-
.toList(), null);
61-
}
62-
63-
private static boolean isJakartaAnnotated(Class<?> cls) {
64-
65-
return cls.isAnnotationPresent(jakarta.persistence.Entity.class)
66-
|| cls.isAnnotationPresent(jakarta.persistence.Embeddable.class)
67-
|| cls.isAnnotationPresent(jakarta.persistence.MappedSuperclass.class)
68-
|| cls.isAnnotationPresent(jakarta.persistence.Converter.class);
69-
}
70-
7158
public AotMetamodel(PersistenceManagedTypes managedTypes) {
7259
this(managedTypes.getManagedClassNames(), managedTypes.getPersistenceUnitRootUrl());
7360
}

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/aot/JpaRepositoryContributor.java

Lines changed: 209 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,22 @@
1515
*/
1616
package org.springframework.data.jpa.repository.aot;
1717

18+
import jakarta.persistence.Converter;
19+
import jakarta.persistence.Embeddable;
20+
import jakarta.persistence.Entity;
1821
import jakarta.persistence.EntityManager;
1922
import jakarta.persistence.EntityManagerFactory;
23+
import jakarta.persistence.MappedSuperclass;
2024
import jakarta.persistence.PersistenceUnitUtil;
2125
import jakarta.persistence.metamodel.Metamodel;
2226
import jakarta.persistence.spi.PersistenceUnitInfo;
2327

2428
import java.lang.reflect.Method;
29+
import java.util.List;
2530
import java.util.Map;
2631
import java.util.Optional;
2732
import java.util.function.Function;
33+
import java.util.function.Supplier;
2834

2935
import org.jspecify.annotations.Nullable;
3036

@@ -59,10 +65,12 @@
5965
import org.springframework.data.repository.query.ParametersSource;
6066
import org.springframework.data.repository.query.QueryMethod;
6167
import org.springframework.data.repository.query.ReturnedType;
68+
import org.springframework.data.util.Lazy;
6269
import org.springframework.javapoet.CodeBlock;
6370
import org.springframework.javapoet.TypeName;
6471
import org.springframework.orm.jpa.persistenceunit.PersistenceManagedTypes;
6572
import org.springframework.util.ClassUtils;
73+
import org.springframework.util.ObjectUtils;
6674
import org.springframework.util.StringUtils;
6775

6876
/**
@@ -77,39 +85,31 @@
7785
*/
7886
public class JpaRepositoryContributor extends RepositoryContributor {
7987

88+
private final AotRepositoryContext context;
89+
private final PersistenceUnitContext persistenceUnit;
8090
private final Metamodel metamodel;
8191
private final PersistenceUnitUtil persistenceUnitUtil;
8292
private final PersistenceProvider persistenceProvider;
8393
private final QueriesFactory queriesFactory;
8494
private final EntityGraphLookup entityGraphLookup;
85-
private final AotRepositoryContext context;
8695

8796
public JpaRepositoryContributor(AotRepositoryContext repositoryContext) {
88-
this(repositoryContext, new AotMetamodel(repositoryContext));
89-
}
90-
91-
public JpaRepositoryContributor(AotRepositoryContext repositoryContext, PersistenceUnitInfo unitInfo) {
92-
this(repositoryContext, new AotMetamodel(unitInfo));
93-
}
94-
95-
public JpaRepositoryContributor(AotRepositoryContext repositoryContext, PersistenceManagedTypes managedTypes) {
96-
this(repositoryContext, new AotMetamodel(managedTypes));
97+
this(repositoryContext, PersistenceUnitContextFactory.from(repositoryContext).create());
9798
}
9899

99100
public JpaRepositoryContributor(AotRepositoryContext repositoryContext, EntityManagerFactory entityManagerFactory) {
100-
this(repositoryContext, entityManagerFactory, entityManagerFactory.getMetamodel());
101+
this(repositoryContext, PersistenceUnitContext.just(entityManagerFactory));
101102
}
102103

103-
private JpaRepositoryContributor(AotRepositoryContext repositoryContext, AotMetamodel metamodel) {
104-
this(repositoryContext, metamodel.getEntityManagerFactory(), metamodel);
105-
}
106-
107-
private JpaRepositoryContributor(AotRepositoryContext repositoryContext, EntityManagerFactory entityManagerFactory,
108-
Metamodel metamodel) {
104+
public JpaRepositoryContributor(AotRepositoryContext repositoryContext, PersistenceUnitContext persistenceUnit) {
109105

110106
super(repositoryContext);
111107

112-
this.metamodel = metamodel;
108+
this.persistenceUnit = persistenceUnit;
109+
this.metamodel = persistenceUnit.getMetamodel();
110+
111+
EntityManagerFactory entityManagerFactory = persistenceUnit.getEntityManagerFactory();
112+
113113
this.persistenceUnitUtil = entityManagerFactory.getPersistenceUnitUtil();
114114
this.persistenceProvider = PersistenceProvider.fromEntityManagerFactory(entityManagerFactory);
115115
this.queriesFactory = new QueriesFactory(repositoryContext.getConfigurationSource(), entityManagerFactory,
@@ -258,8 +258,195 @@ private Optional<Class<QueryEnhancerSelector>> getQueryEnhancerSelectorClass() {
258258
});
259259
}
260260

261-
public Metamodel getMetamodel() {
262-
return metamodel;
261+
public PersistenceUnitContext getPersistenceUnit() {
262+
return persistenceUnit;
263+
}
264+
265+
/**
266+
* Factory for deferred {@link PersistenceUnitContext} creation. Factory objects implement equality checks based on
267+
* their creation and can be used conveniently as cache keys.
268+
*/
269+
public static class PersistenceUnitContextFactory {
270+
271+
private final Supplier<? extends PersistenceUnitContext> factory;
272+
private final Object key;
273+
274+
private PersistenceUnitContextFactory(Supplier<? extends PersistenceUnitContext> factory, Object key) {
275+
this.factory = Lazy.of(factory);
276+
this.key = key;
277+
}
278+
279+
/**
280+
* Create a {@code PersistenceUnitContext} from the given {@link AotRepositoryContext} using Jakarta
281+
* Persistence-annotated classes.
282+
*
283+
* @param repositoryContext repository context providing classes.
284+
*/
285+
public static PersistenceUnitContextFactory from(AotRepositoryContext repositoryContext) {
286+
287+
List<String> typeNames = repositoryContext.getResolvedTypes().stream()
288+
.filter(PersistenceUnitContextFactory::isJakartaAnnotated).map(Class::getName).toList();
289+
290+
return from(() -> new AotMetamodel(PersistenceManagedTypes.of(typeNames, List.of())), typeNames);
291+
}
292+
293+
/**
294+
* Create a {@code PersistenceUnitContext} from the given {@link PersistenceUnitInfo}.
295+
*
296+
* @param persistenceUnitInfo persistence unit info to use.
297+
*/
298+
public static PersistenceUnitContextFactory from(PersistenceUnitInfo persistenceUnitInfo) {
299+
return from(() -> new AotMetamodel(persistenceUnitInfo), persistenceUnitInfo);
300+
}
301+
302+
/**
303+
* Create a {@code PersistenceUnitContext} from the given {@link PersistenceManagedTypes}.
304+
*
305+
* @param managedTypes managed types to use.
306+
*/
307+
public static PersistenceUnitContextFactory from(PersistenceManagedTypes managedTypes) {
308+
return from(() -> new AotMetamodel(managedTypes), managedTypes);
309+
}
310+
311+
/**
312+
* Create a {@code PersistenceUnitContext} from the given {@link EntityManagerFactory} and its {@link Metamodel}.
313+
*
314+
* @param entityManagerFactory the entity manager factory to use.
315+
*/
316+
public static PersistenceUnitContextFactory just(EntityManagerFactory entityManagerFactory) {
317+
return new PersistenceUnitContextFactory(() -> new EntityManagerPersistenceUnitContext(entityManagerFactory),
318+
entityManagerFactory.getMetamodel());
319+
}
320+
321+
/**
322+
* Create a {@code PersistenceUnitContext} from the given {@link EntityManagerFactory} and its {@link Metamodel}.
323+
*
324+
* @param metamodel the metamodel to use.
325+
* @param entityManagerFactory the entity manager factory to use.
326+
*/
327+
public static PersistenceUnitContextFactory just(EntityManagerFactory entityManagerFactory, Metamodel metamodel) {
328+
return new PersistenceUnitContextFactory(
329+
() -> new EntityManagerPersistenceUnitContext(entityManagerFactory, metamodel), metamodel);
330+
}
331+
332+
private static PersistenceUnitContextFactory from(Supplier<? extends AotMetamodel> metamodel, Object key) {
333+
return new PersistenceUnitContextFactory(() -> new AotMetamodelContext(metamodel.get()), key);
334+
}
335+
336+
private static boolean isJakartaAnnotated(Class<?> cls) {
337+
338+
return cls.isAnnotationPresent(Entity.class) //
339+
|| cls.isAnnotationPresent(Embeddable.class) //
340+
|| cls.isAnnotationPresent(MappedSuperclass.class) //
341+
|| cls.isAnnotationPresent(Converter.class);
342+
}
343+
344+
public PersistenceUnitContext create() {
345+
return factory.get();
346+
}
347+
348+
@Override
349+
public boolean equals(Object o) {
350+
if (!(o instanceof PersistenceUnitContextFactory that)) {
351+
return false;
352+
}
353+
return ObjectUtils.nullSafeEquals(key, that.key);
354+
}
355+
356+
@Override
357+
public int hashCode() {
358+
return ObjectUtils.nullSafeHashCode(key);
359+
}
360+
361+
@Override
362+
public String toString() {
363+
return "PersistenceUnitContextFactory{" + key + '}';
364+
}
365+
366+
}
367+
368+
/**
369+
* Strategy interface representing a JPA PersistenceUnit providing access to {@link EntityManagerFactory} and
370+
* {@link Metamodel} for AOT repository generation.
371+
*/
372+
public interface PersistenceUnitContext {
373+
374+
/**
375+
* @return the entity manager factory.
376+
*/
377+
EntityManagerFactory getEntityManagerFactory();
378+
379+
/**
380+
* @return metamodel describing managed types used in the persistence unit.
381+
*/
382+
Metamodel getMetamodel();
383+
384+
/**
385+
* Create a {@code PersistenceUnitContext} from the given {@link PersistenceUnitInfo}.
386+
*
387+
* @param persistenceUnitInfo persistence unit info to use.
388+
*/
389+
static PersistenceUnitContext from(PersistenceUnitInfo persistenceUnitInfo) {
390+
return new AotMetamodelContext(new AotMetamodel(persistenceUnitInfo));
391+
}
392+
393+
/**
394+
* Create a {@code PersistenceUnitContext} from the given {@link PersistenceManagedTypes}.
395+
*
396+
* @param managedTypes managed types to use.
397+
*/
398+
static PersistenceUnitContext from(PersistenceManagedTypes managedTypes) {
399+
return new AotMetamodelContext(new AotMetamodel(managedTypes));
400+
}
401+
402+
/**
403+
* Create a {@code PersistenceUnitContext} from the given {@link EntityManagerFactory} and its {@link Metamodel}.
404+
*
405+
* @param entityManagerFactory the entity manager factory to use.
406+
*/
407+
static PersistenceUnitContext just(EntityManagerFactory entityManagerFactory) {
408+
return new EntityManagerPersistenceUnitContext(entityManagerFactory);
409+
}
410+
411+
}
412+
413+
/**
414+
* Persistence unit context backed by an {@link EntityManagerFactory}.
415+
*/
416+
record EntityManagerPersistenceUnitContext(EntityManagerFactory factory,
417+
Metamodel metamodel) implements PersistenceUnitContext {
418+
419+
public EntityManagerPersistenceUnitContext(EntityManagerFactory factory) {
420+
this(factory, factory.getMetamodel());
421+
}
422+
423+
@Override
424+
public Metamodel getMetamodel() {
425+
return metamodel();
426+
}
427+
428+
@Override
429+
public EntityManagerFactory getEntityManagerFactory() {
430+
return factory();
431+
}
432+
433+
}
434+
435+
/**
436+
* Persistence unit context backed by an {@link AotMetamodel}.
437+
*/
438+
private record AotMetamodelContext(AotMetamodel metamodel) implements PersistenceUnitContext {
439+
440+
@Override
441+
public EntityManagerFactory getEntityManagerFactory() {
442+
return metamodel.getEntityManagerFactory();
443+
}
444+
445+
@Override
446+
public Metamodel getMetamodel() {
447+
return metamodel;
448+
}
449+
263450
}
264451

265452
record StoredProcedureMetadata(String procedure) implements QueryMetadata {
@@ -268,6 +455,7 @@ record StoredProcedureMetadata(String procedure) implements QueryMetadata {
268455
public Map<String, Object> serialize() {
269456
return Map.of("procedure", procedure());
270457
}
458+
271459
}
272460

273461
record NamedStoredProcedureMetadata(String procedureName) implements QueryMetadata {
@@ -276,6 +464,7 @@ record NamedStoredProcedureMetadata(String procedureName) implements QueryMetada
276464
public Map<String, Object> serialize() {
277465
return Map.of("procedure-name", procedureName());
278466
}
467+
279468
}
280469

281470
/**

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtension.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
import org.springframework.data.aot.AotContext;
6262
import org.springframework.data.jpa.repository.JpaRepository;
6363
import org.springframework.data.jpa.repository.aot.JpaRepositoryContributor;
64+
import org.springframework.data.jpa.repository.aot.JpaRepositoryContributor.PersistenceUnitContextFactory;
6465
import org.springframework.data.jpa.repository.support.DefaultJpaContext;
6566
import org.springframework.data.jpa.repository.support.JpaEvaluationContextExtension;
6667
import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;
@@ -75,6 +76,7 @@
7576
import org.springframework.orm.jpa.persistenceunit.PersistenceManagedTypes;
7677
import org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor;
7778
import org.springframework.util.ClassUtils;
79+
import org.springframework.util.ConcurrentLruCache;
7880
import org.springframework.util.ObjectUtils;
7981
import org.springframework.util.StringUtils;
8082

@@ -378,6 +380,9 @@ public static class JpaRepositoryRegistrationAotProcessor extends RepositoryRegi
378380

379381
private static final String MODULE_NAME = "jpa";
380382

383+
private static final ConcurrentLruCache<PersistenceUnitContextFactory, JpaRepositoryContributor.PersistenceUnitContext> factoryCache = new ConcurrentLruCache<>(
384+
16, PersistenceUnitContextFactory::create);
385+
381386
@Override
382387
protected void configureTypeContributions(AotRepositoryContext repositoryContext,
383388
GenerationContext generationContext) {
@@ -421,7 +426,7 @@ protected void configureTypeContribution(Class<?> type, AotContext aotContext) {
421426
if (managedTypes != null) {
422427

423428
log.debug("Using PersistenceManagedTypes for AOT repository generation");
424-
return new JpaRepositoryContributor(repositoryContext, managedTypes);
429+
return contribute(repositoryContext, PersistenceUnitContextFactory.from(managedTypes));
425430
}
426431

427432
ObjectProvider<PersistenceUnitInfo> infoProvider = beanFactory.getBeanProvider(PersistenceUnitInfo.class);
@@ -430,11 +435,18 @@ protected void configureTypeContribution(Class<?> type, AotContext aotContext) {
430435
if (unitInfo != null) {
431436

432437
log.debug("Using PersistenceUnitInfo for AOT repository generation");
433-
return new JpaRepositoryContributor(repositoryContext, unitInfo);
438+
return contribute(repositoryContext, PersistenceUnitContextFactory.from(unitInfo));
434439
}
435440

436441
log.debug("Using scanned types for AOT repository generation");
437-
return new JpaRepositoryContributor(repositoryContext);
442+
return contribute(repositoryContext, PersistenceUnitContextFactory.from(repositoryContext));
443+
}
444+
445+
private JpaRepositoryContributor contribute(AotRepositoryContext repositoryContext,
446+
PersistenceUnitContextFactory factory) {
447+
448+
JpaRepositoryContributor.PersistenceUnitContext persistenceUnitContext = factoryCache.get(factory);
449+
return new JpaRepositoryContributor(repositoryContext, persistenceUnitContext);
438450
}
439451

440452
}

spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/config/JpaRepositoryRegistrationAotProcessorUnitTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ public List<String> getManagedPackages() {
131131
JpaRepositoryContributor contributor = new JpaRepositoryConfigExtension.JpaRepositoryRegistrationAotProcessor()
132132
.contributeAotRepository(new DummyAotRepositoryContext(context));
133133

134-
assertThat(contributor.getMetamodel().managedType(Person.class)).isNotNull();
134+
assertThat(contributor.getPersistenceUnit().getMetamodel().managedType(Person.class)).isNotNull();
135135
}
136136

137137
@Test // GH-3899

0 commit comments

Comments
 (0)