Skip to content

Commit 5af0c40

Browse files
committed
Consistently use JPA metamodel to determine entity name.
We now use JpaMetamodelEntityMetadata where possible to determine the entity name. Previously, we defaulted in many places to DefaultJpaEntityMetadata not considering XML-based entity names and also using improper unqualifying of entity names that lead to usage of Class.getSimpleName() without considering inner class prefixes. While this commit introduces a consistent scheme, we still have to resolve some package tangles and improve our design as entity information duplicates parts of JpaPersistentEntity and causing duplicate introspections for JpaEntityInformationSupport. We will have to revisit the design with #4037. Closes #4032
1 parent 596e07e commit 5af0c40

19 files changed

+369
-162
lines changed

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

Lines changed: 47 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@
1717

1818
import jakarta.persistence.EntityManager;
1919
import jakarta.persistence.EntityManagerFactory;
20+
import jakarta.persistence.PersistenceUnitUtil;
2021
import jakarta.persistence.metamodel.Metamodel;
2122
import jakarta.persistence.spi.PersistenceUnitInfo;
2223

2324
import java.lang.reflect.Method;
2425
import java.util.Map;
2526
import java.util.Optional;
27+
import java.util.function.Function;
2628

2729
import org.jspecify.annotations.Nullable;
2830

@@ -32,22 +34,28 @@
3234
import org.springframework.core.annotation.MergedAnnotation;
3335
import org.springframework.core.annotation.MergedAnnotations;
3436
import org.springframework.data.jpa.provider.PersistenceProvider;
37+
import org.springframework.data.jpa.provider.QueryExtractor;
3538
import org.springframework.data.jpa.repository.EntityGraph;
3639
import org.springframework.data.jpa.repository.Modifying;
3740
import org.springframework.data.jpa.repository.NativeQuery;
3841
import org.springframework.data.jpa.repository.Query;
3942
import org.springframework.data.jpa.repository.QueryHints;
43+
import org.springframework.data.jpa.repository.query.JpaEntityMetadata;
4044
import org.springframework.data.jpa.repository.query.JpaParameters;
4145
import org.springframework.data.jpa.repository.query.JpaQueryMethod;
4246
import org.springframework.data.jpa.repository.query.Procedure;
4347
import org.springframework.data.jpa.repository.query.QueryEnhancerSelector;
48+
import org.springframework.data.jpa.repository.support.JpaEntityInformationSupport;
49+
import org.springframework.data.projection.ProjectionFactory;
4450
import org.springframework.data.repository.aot.generate.AotRepositoryClassBuilder;
4551
import org.springframework.data.repository.aot.generate.AotRepositoryConstructorBuilder;
4652
import org.springframework.data.repository.aot.generate.MethodContributor;
4753
import org.springframework.data.repository.aot.generate.QueryMetadata;
4854
import org.springframework.data.repository.aot.generate.RepositoryContributor;
4955
import org.springframework.data.repository.config.AotRepositoryContext;
56+
import org.springframework.data.repository.core.RepositoryMetadata;
5057
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
58+
import org.springframework.data.repository.query.ParametersSource;
5159
import org.springframework.data.repository.query.QueryMethod;
5260
import org.springframework.data.repository.query.ReturnedType;
5361
import org.springframework.data.util.TypeInformation;
@@ -70,6 +78,7 @@
7078
public class JpaRepositoryContributor extends RepositoryContributor {
7179

7280
private final Metamodel metamodel;
81+
private final PersistenceUnitUtil persistenceUnitUtil;
7382
private final PersistenceProvider persistenceProvider;
7483
private final QueriesFactory queriesFactory;
7584
private final EntityGraphLookup entityGraphLookup;
@@ -88,28 +97,24 @@ public JpaRepositoryContributor(AotRepositoryContext repositoryContext, Persiste
8897
}
8998

9099
public JpaRepositoryContributor(AotRepositoryContext repositoryContext, EntityManagerFactory entityManagerFactory) {
91-
this(repositoryContext, entityManagerFactory.getMetamodel(),
92-
PersistenceProvider.fromEntityManagerFactory(entityManagerFactory),
93-
new QueriesFactory(repositoryContext.getConfigurationSource(), entityManagerFactory,
94-
repositoryContext.getRequiredClassLoader()),
95-
new EntityGraphLookup(entityManagerFactory));
100+
this(repositoryContext, entityManagerFactory, entityManagerFactory.getMetamodel());
96101
}
97102

98103
private JpaRepositoryContributor(AotRepositoryContext repositoryContext, AotMetamodel metamodel) {
99-
this(repositoryContext, metamodel,
100-
PersistenceProvider.fromEntityManagerFactory(metamodel.getEntityManagerFactory()),
101-
new QueriesFactory(repositoryContext.getConfigurationSource(), metamodel.getEntityManagerFactory(),
102-
repositoryContext.getRequiredClassLoader()),
103-
new EntityGraphLookup(metamodel.getEntityManagerFactory()));
104+
this(repositoryContext, metamodel.getEntityManagerFactory(), metamodel);
104105
}
105106

106-
private JpaRepositoryContributor(AotRepositoryContext repositoryContext, Metamodel metamodel,
107-
PersistenceProvider persistenceProvider, QueriesFactory queriesFactory, EntityGraphLookup entityGraphLookup) {
107+
private JpaRepositoryContributor(AotRepositoryContext repositoryContext, EntityManagerFactory entityManagerFactory,
108+
Metamodel metamodel) {
109+
108110
super(repositoryContext);
111+
109112
this.metamodel = metamodel;
110-
this.persistenceProvider = persistenceProvider;
111-
this.queriesFactory = queriesFactory;
112-
this.entityGraphLookup = entityGraphLookup;
113+
this.persistenceUnitUtil = entityManagerFactory.getPersistenceUnitUtil();
114+
this.persistenceProvider = PersistenceProvider.fromEntityManagerFactory(entityManagerFactory);
115+
this.queriesFactory = new QueriesFactory(repositoryContext.getConfigurationSource(), entityManagerFactory,
116+
repositoryContext.getRequiredClassLoader());
117+
this.entityGraphLookup = new EntityGraphLookup(entityManagerFactory);
113118
this.context = repositoryContext;
114119
}
115120

@@ -159,8 +164,10 @@ private Optional<Class<QueryEnhancerSelector>> getQueryEnhancerSelectorClass() {
159164
@Override
160165
protected @Nullable MethodContributor<? extends QueryMethod> contributeQueryMethod(Method method) {
161166

162-
JpaQueryMethod queryMethod = new JpaQueryMethod(method, getRepositoryInformation(), getProjectionFactory(),
163-
persistenceProvider);
167+
JpaEntityMetadata<?> entityInformation = JpaEntityInformationSupport
168+
.getEntityInformation(getRepositoryInformation().getDomainType(), metamodel, persistenceUnitUtil);
169+
AotJpaQueryMethod queryMethod = new AotJpaQueryMethod(method, getRepositoryInformation(), entityInformation,
170+
getProjectionFactory(), persistenceProvider, JpaParameters::new);
164171

165172
Optional<Class<QueryEnhancerSelector>> queryEnhancerSelectorClass = getQueryEnhancerSelectorClass();
166173
QueryEnhancerSelector selector = queryEnhancerSelectorClass.map(BeanUtils::instantiateClass)
@@ -271,4 +278,27 @@ public Map<String, Object> serialize() {
271278
}
272279
}
273280

281+
/**
282+
* AOT extension to {@link JpaQueryMethod} providing a metamodel backed {@link JpaEntityMetadata} object.
283+
*/
284+
static class AotJpaQueryMethod extends JpaQueryMethod {
285+
286+
private final JpaEntityMetadata<?> entityMetadata;
287+
288+
public AotJpaQueryMethod(Method method, RepositoryMetadata metadata, JpaEntityMetadata<?> entityMetadata,
289+
ProjectionFactory factory, QueryExtractor extractor,
290+
Function<ParametersSource, JpaParameters> parametersFunction) {
291+
292+
super(method, metadata, factory, extractor, parametersFunction);
293+
294+
this.entityMetadata = entityMetadata;
295+
}
296+
297+
@Override
298+
public JpaEntityMetadata<?> getEntityInformation() {
299+
return this.entityMetadata;
300+
}
301+
302+
}
303+
274304
}

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

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,8 @@ private AotQueries buildPartTreeQuery(RepositoryInformation repositoryInformatio
271271
MergedAnnotation<Query> query, JpaQueryMethod queryMethod) {
272272

273273
PartTree partTree = new PartTree(queryMethod.getName(), repositoryInformation.getDomainType());
274-
AotQuery aotQuery = createQuery(partTree, returnedType, queryMethod.getParameters(), templates);
274+
AotQuery aotQuery = createQuery(partTree, returnedType, queryMethod.getParameters(), templates,
275+
queryMethod.getEntityInformation());
275276

276277
if (query.isPresent() && StringUtils.hasText(query.getString("countQuery"))) {
277278
return AotQueries.from(aotQuery, StringAotQuery.of(DeclaredQuery.jpqlQuery(query.getString("countQuery"))));
@@ -282,27 +283,28 @@ private AotQueries buildPartTreeQuery(RepositoryInformation repositoryInformatio
282283
createNamedAotQuery(returnedType, selector, queryMethod.getNamedCountQueryName(), queryMethod, false));
283284
}
284285

285-
AotQuery partTreeCountQuery = createCountQuery(partTree, returnedType, queryMethod.getParameters(), templates);
286+
AotQuery partTreeCountQuery = createCountQuery(partTree, returnedType, queryMethod.getParameters(), templates,
287+
queryMethod.getEntityInformation());
286288
return AotQueries.from(aotQuery, partTreeCountQuery);
287289
}
288290

289291
private AotQuery createQuery(PartTree partTree, ReturnedType returnedType, JpaParameters parameters,
290-
JpqlQueryTemplates templates) {
292+
JpqlQueryTemplates templates, JpaEntityMetadata<?> entityMetadata) {
291293

292294
ParameterMetadataProvider metadataProvider = new ParameterMetadataProvider(parameters, escapeCharacter, templates);
293295
JpaQueryCreator queryCreator = new JpaQueryCreator(partTree, false, returnedType, metadataProvider, templates,
294-
metamodel);
296+
entityMetadata, metamodel);
295297

296298
return StringAotQuery.jpqlQuery(queryCreator.createQuery(), metadataProvider.getBindings(),
297299
partTree.getResultLimit(), partTree.isDelete(), partTree.isExistsProjection());
298300
}
299301

300302
private AotQuery createCountQuery(PartTree partTree, ReturnedType returnedType, JpaParameters parameters,
301-
JpqlQueryTemplates templates) {
303+
JpqlQueryTemplates templates, JpaEntityMetadata<?> entityMetadata) {
302304

303305
ParameterMetadataProvider metadataProvider = new ParameterMetadataProvider(parameters, escapeCharacter, templates);
304306
JpaQueryCreator queryCreator = new JpaCountQueryCreator(partTree, returnedType, metadataProvider, templates,
305-
metamodel);
307+
entityMetadata, metamodel);
306308

307309
return StringAotQuery.jpqlQuery(queryCreator.createQuery(), metadataProvider.getBindings(), Limit.unlimited(),
308310
false, false);
@@ -333,7 +335,6 @@ private AotQuery createCountQuery(PartTree partTree, ReturnedType returnedType,
333335
return returnedType.getReturnedType();
334336
}
335337

336-
337338
return result;
338339
}
339340

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DefaultJpaEntityMetadata.java

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919

2020
import java.util.function.Function;
2121

22+
import org.jspecify.annotations.Nullable;
23+
2224
import org.springframework.core.annotation.AnnotatedElementUtils;
2325
import org.springframework.util.Assert;
2426
import org.springframework.util.StringUtils;
@@ -32,6 +34,7 @@
3234
public class DefaultJpaEntityMetadata<T> implements JpaEntityMetadata<T> {
3335

3436
private final Class<T> domainType;
37+
private final @Nullable Entity entity;
3538

3639
/**
3740
* Creates a new {@link DefaultJpaEntityMetadata} for the given domain type.
@@ -41,7 +44,9 @@ public class DefaultJpaEntityMetadata<T> implements JpaEntityMetadata<T> {
4144
public DefaultJpaEntityMetadata(Class<T> domainType) {
4245

4346
Assert.notNull(domainType, "Domain type must not be null");
47+
4448
this.domainType = domainType;
49+
this.entity = AnnotatedElementUtils.findMergedAnnotation(domainType, Entity.class);
4550
}
4651

4752
@Override
@@ -51,13 +56,20 @@ public Class<T> getJavaType() {
5156

5257
@Override
5358
public String getEntityName() {
54-
return getEntityNameOr(Class::getSimpleName);
59+
return getEntityNameOr(DefaultJpaEntityMetadata::unqualify);
5560
}
5661

57-
String getEntityNameOr(Function<Class<?>, String> alternative) {
62+
private String getEntityNameOr(Function<Class<?>, String> alternative) {
63+
return (entity != null && StringUtils.hasText(entity.name())) ? entity.name() : alternative.apply(domainType);
64+
}
65+
66+
static String unqualify(Class<?> clazz) {
67+
return unqualify(clazz.getName());
68+
}
5869

59-
Entity entity = AnnotatedElementUtils.findMergedAnnotation(domainType, Entity.class);
60-
return null != entity && StringUtils.hasText(entity.name()) ? entity.name() : alternative.apply(domainType);
70+
static String unqualify(String qualifiedName) {
71+
int loc = qualifiedName.lastIndexOf('.');
72+
return loc < 0 ? qualifiedName : qualifiedName.substring(loc + 1);
6173
}
6274

6375
}

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaCountQueryCreator.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434
public class JpaCountQueryCreator extends JpaQueryCreator {
3535

3636
private final boolean distinct;
37-
private final ReturnedType returnedType;
3837

3938
/**
4039
* Creates a new {@link JpaCountQueryCreator}
@@ -51,7 +50,6 @@ public JpaCountQueryCreator(PartTree tree, ReturnedType returnedType, ParameterM
5150
super(tree, returnedType, provider, templates, em.getMetamodel());
5251

5352
this.distinct = tree.isDistinct();
54-
this.returnedType = returnedType;
5553
}
5654

5755
/**
@@ -69,12 +67,21 @@ public JpaCountQueryCreator(PartTree tree, ReturnedType returnedType, ParameterM
6967
super(tree, returnedType, provider, templates, metamodel);
7068

7169
this.distinct = tree.isDistinct();
72-
this.returnedType = returnedType;
70+
}
71+
72+
public JpaCountQueryCreator(PartTree tree, ReturnedType returnedType, ParameterMetadataProvider provider,
73+
JpqlQueryTemplates templates, JpaEntityMetadata<?> entityMetadata, Metamodel metamodel) {
74+
75+
super(tree, false, returnedType, provider, templates, entityMetadata, metamodel);
76+
77+
this.distinct = tree.isDistinct();
7378
}
7479

7580
@Override
7681
protected JpqlQueryBuilder.Select buildQuery(Sort sort) {
77-
JpqlQueryBuilder.SelectStep selectStep = JpqlQueryBuilder.selectFrom(returnedType.getDomainType());
82+
83+
JpqlQueryBuilder.SelectStep selectStep = JpqlQueryBuilder.selectFrom(getEntity());
84+
7885
if (this.distinct) {
7986
selectStep = selectStep.distinct();
8087
}

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaKeysetScrollQueryCreator.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.springframework.data.jpa.repository.query;
1717

1818
import jakarta.persistence.EntityManager;
19+
import jakarta.persistence.metamodel.Metamodel;
1920

2021
import java.util.ArrayList;
2122
import java.util.Collection;
@@ -40,6 +41,7 @@
4041
*/
4142
class JpaKeysetScrollQueryCreator extends JpaQueryCreator {
4243

44+
private final Metamodel metamodel;
4345
private final JpaEntityInformation<?, ?> entityInformation;
4446
private final KeysetScrollPosition scrollPosition;
4547
private final ParameterMetadataProvider provider;
@@ -49,8 +51,9 @@ public JpaKeysetScrollQueryCreator(PartTree tree, ReturnedType type, ParameterMe
4951
JpqlQueryTemplates templates, JpaEntityInformation<?, ?> entityInformation, KeysetScrollPosition scrollPosition,
5052
EntityManager em) {
5153

52-
super(tree, type, provider, templates, em.getMetamodel());
54+
super(tree, false, type, provider, templates, entityInformation, em.getMetamodel());
5355

56+
this.metamodel = em.getMetamodel();
5457
this.entityInformation = entityInformation;
5558
this.scrollPosition = scrollPosition;
5659
this.provider = provider;
@@ -76,7 +79,7 @@ protected JpqlQueryBuilder.AbstractJpqlQuery createQuery(JpqlQueryBuilder.@Nulla
7679
JpqlQueryBuilder.Select query = buildQuery(keysetSpec.sort());
7780

7881
Map<String, Map<Object, ParameterBinding>> cachedBindings = new LinkedHashMap<>();
79-
JpqlQueryBuilder.Predicate keysetPredicate = keysetSpec.createJpqlPredicate(getFrom(), getEntity(),
82+
JpqlQueryBuilder.Predicate keysetPredicate = keysetSpec.createJpqlPredicate(metamodel, getFrom(), getEntity(),
8083
(property, value) -> {
8184

8285
Map<Object, ParameterBinding> bindings = cachedBindings.computeIfAbsent(property, k -> new LinkedHashMap<>());
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright 2013-2025 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+
package org.springframework.data.jpa.repository.query;
17+
18+
import jakarta.persistence.metamodel.EntityType;
19+
20+
import org.springframework.util.Assert;
21+
22+
/**
23+
* Metamodel-based implementation for {@link JpaEntityMetadata}.
24+
*
25+
* @author Mark Paluch
26+
* @since 4.0
27+
*/
28+
public class JpaMetamodelEntityMetadata<T> implements JpaEntityMetadata<T> {
29+
30+
private final EntityType<T> entityType;
31+
32+
/**
33+
* Creates a new {@link JpaMetamodelEntityMetadata} for the given domain type.
34+
*
35+
* @param entityType must not be {@literal null}.
36+
*/
37+
public JpaMetamodelEntityMetadata(EntityType<T> entityType) {
38+
39+
Assert.notNull(entityType, "Entity type must not be null");
40+
this.entityType = entityType;
41+
}
42+
43+
@Override
44+
public Class<T> getJavaType() {
45+
return entityType.getJavaType();
46+
}
47+
48+
@Override
49+
public String getEntityName() {
50+
return entityType.getName();
51+
}
52+
53+
}

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,15 @@ public JpaQueryCreator(PartTree tree, ReturnedType type, ParameterMetadataProvid
115115
this(tree, false, type, provider, templates, metamodel);
116116
}
117117

118+
@SuppressWarnings({ "rawtypes", "unchecked" })
118119
public JpaQueryCreator(PartTree tree, boolean searchQuery, ReturnedType type, ParameterMetadataProvider provider,
119120
JpqlQueryTemplates templates, Metamodel metamodel) {
121+
this(tree, searchQuery, type, provider, templates,
122+
new JpaMetamodelEntityMetadata(metamodel.entity(type.getDomainType())), metamodel);
123+
}
124+
125+
public JpaQueryCreator(PartTree tree, boolean searchQuery, ReturnedType type, ParameterMetadataProvider provider,
126+
JpqlQueryTemplates templates, JpaEntityMetadata<?> entityMetadata, Metamodel metamodel) {
120127

121128
super(tree);
122129

@@ -144,7 +151,7 @@ public JpaQueryCreator(PartTree tree, boolean searchQuery, ReturnedType type, Pa
144151
this.templates = templates;
145152
this.escape = provider.getEscape();
146153
this.entityType = metamodel.entity(type.getDomainType());
147-
this.entity = JpqlQueryBuilder.entity(returnedType.getDomainType());
154+
this.entity = JpqlQueryBuilder.entity(entityMetadata);
148155
this.metamodel = metamodel;
149156
this.similarityNormalizer = provider.getSimilarityNormalizer();
150157
}

0 commit comments

Comments
 (0)