Skip to content

Commit 5561fd1

Browse files
committed
Refactoring.
Introduce AotRepositoryFragmentSupport, adopt to FragmentCreationContext. Reduce visibility. Refactor CodeBlocks builder. Simplify query rewriting and use base class methods. Use typed verifier through a JDK proxy to avoid reflective frontend. Revise testing to a plain old Spring test but testing the AOT fragment through its interface by forwarding reflective calls to the AOT fragment. Refactor AotQuery into AotQueries to support a wider range of possible queries. See #3830
1 parent 540f3fb commit 5561fd1

20 files changed

+1198
-965
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
/**
3838
* @author Christoph Strobl
3939
*/
40-
public class AotMetaModel implements Metamodel {
40+
class AotMetaModel implements Metamodel {
4141

4242
private final String persistenceUnit;
4343
private final Set<Class<?>> managedTypes;
@@ -105,7 +105,7 @@ public ClassLoader getNewTempClassLoader() {
105105

106106
@Override
107107
public void addTransformer(ClassTransformer classTransformer) {
108-
// just ingnore it
108+
// just ignore it
109109
}
110110
};
111111

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright 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.aot.generated;
17+
18+
import jakarta.validation.constraints.Null;
19+
20+
import org.springframework.data.jpa.repository.query.DeclaredQuery;
21+
import org.springframework.data.jpa.repository.query.QueryEnhancer;
22+
import org.springframework.data.jpa.repository.query.QueryEnhancerSelector;
23+
import org.springframework.util.StringUtils;
24+
25+
/**
26+
* Value object capturing queries used for repository query methods.
27+
*
28+
* @author Mark Paluch
29+
* @since 4.0
30+
*/
31+
record AotQueries(AotQuery result, AotQuery count) {
32+
33+
/**
34+
* Derive a count query from the given query.
35+
*/
36+
public static AotQueries from(StringAotQuery query, @Null String countProjection, QueryEnhancerSelector selector) {
37+
38+
QueryEnhancer queryEnhancer = selector.select(query.getQuery()).create(query.getQuery());
39+
40+
String derivedCountQuery = queryEnhancer
41+
.createCountQueryFor(StringUtils.hasText(countProjection) ? countProjection : null);
42+
43+
DeclaredQuery countQuery = query.getQuery().rewrite(derivedCountQuery);
44+
return new AotQueries(query, StringAotQuery.of(countQuery));
45+
}
46+
47+
/**
48+
* Create new {@code AotQueries} for the given queries.
49+
*/
50+
public static AotQueries from(AotQuery result, AotQuery count) {
51+
return new AotQueries(result, count);
52+
}
53+
54+
public boolean isNative() {
55+
return result().isNative();
56+
}
57+
58+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright 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.aot.generated;
17+
18+
import java.util.List;
19+
20+
import org.springframework.data.domain.Limit;
21+
import org.springframework.data.jpa.repository.query.ParameterBinding;
22+
23+
/**
24+
* AOT query value object along with its parameter bindings.
25+
*
26+
* @author Christoph Strobl
27+
* @author Mark Paluch
28+
* @since 4.0
29+
*/
30+
abstract class AotQuery {
31+
32+
private final List<ParameterBinding> parameterBindings;
33+
34+
AotQuery(List<ParameterBinding> parameterBindings) {
35+
this.parameterBindings = parameterBindings;
36+
}
37+
38+
/**
39+
* @return whether the query is a {@link jakarta.persistence.EntityManager#createNativeQuery native} one.
40+
*/
41+
public abstract boolean isNative();
42+
43+
public List<ParameterBinding> getParameterBindings() {
44+
return parameterBindings;
45+
}
46+
47+
/**
48+
* @return the preliminary query limit.
49+
*/
50+
public Limit getLimit() {
51+
return Limit.unlimited();
52+
}
53+
54+
/**
55+
* @return whether the query is limited (e.g. {@code findTop10By}).
56+
*/
57+
public boolean isLimited() {
58+
return getLimit().isLimited();
59+
}
60+
61+
}

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

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -17,46 +17,18 @@
1717

1818
import jakarta.persistence.metamodel.Metamodel;
1919

20-
import org.springframework.data.jpa.repository.Query;
21-
import org.springframework.data.jpa.repository.query.EscapeCharacter;
22-
import org.springframework.data.jpa.repository.query.JpaParameters;
23-
import org.springframework.data.jpa.repository.query.JpaQueryCreator;
24-
import org.springframework.data.jpa.repository.query.ParameterMetadataProvider;
25-
import org.springframework.data.jpa.repository.support.JpqlQueryTemplates;
26-
import org.springframework.data.repository.aot.generate.AotRepositoryMethodGenerationContext;
27-
import org.springframework.data.repository.query.ParametersSource;
28-
import org.springframework.data.repository.query.ReturnedType;
29-
import org.springframework.data.repository.query.parser.PartTree;
30-
3120
/**
3221
* @author Christoph Strobl
3322
* @since 2025/01
3423
*/
35-
public class AotQueryCreator {
24+
class AotQueryCreator {
3625

3726
Metamodel metamodel;
3827

3928
public AotQueryCreator(Metamodel metamodel) {
4029
this.metamodel = metamodel;
4130
}
4231

43-
AotStringQuery createQuery(PartTree partTree, ReturnedType returnedType,
44-
AotRepositoryMethodGenerationContext context) {
45-
46-
ParametersSource parametersSource = ParametersSource.of(context.getRepositoryInformation(), context.getMethod());
47-
JpaParameters parameters = new JpaParameters(parametersSource);
48-
ParameterMetadataProvider metadataProvider = new ParameterMetadataProvider(parameters, EscapeCharacter.DEFAULT,
49-
JpqlQueryTemplates.UPPER);
5032

51-
JpaQueryCreator queryCreator = new JpaQueryCreator(partTree, returnedType, metadataProvider,
52-
JpqlQueryTemplates.UPPER, metamodel);
53-
AotStringQuery query = AotStringQuery.bindable(queryCreator.createQuery(), metadataProvider.getBindings());
54-
55-
if (partTree.isLimiting()) {
56-
query.setLimit(partTree.getResultLimit());
57-
}
58-
query.setCountQuery(context.annotationValue(Query.class, "countQuery"));
59-
return query;
60-
}
6133

6234
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/*
2+
* Copyright 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.aot.generated;
17+
18+
import java.lang.reflect.Method;
19+
20+
import org.jspecify.annotations.Nullable;
21+
22+
import org.springframework.data.domain.Sort;
23+
import org.springframework.data.expression.ValueEvaluationContextProvider;
24+
import org.springframework.data.expression.ValueExpression;
25+
import org.springframework.data.jpa.repository.query.DeclaredQuery;
26+
import org.springframework.data.jpa.repository.query.JpaParameters;
27+
import org.springframework.data.jpa.repository.query.QueryEnhancer;
28+
import org.springframework.data.jpa.repository.query.QueryEnhancerSelector;
29+
import org.springframework.data.projection.ProjectionFactory;
30+
import org.springframework.data.repository.core.RepositoryMetadata;
31+
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
32+
import org.springframework.data.repository.query.ParametersSource;
33+
import org.springframework.data.repository.query.ReturnedType;
34+
import org.springframework.data.repository.query.ValueExpressionDelegate;
35+
import org.springframework.util.ConcurrentLruCache;
36+
37+
/**
38+
* @author Mark Paluch
39+
*/
40+
public class AotRepositoryFragmentSupport {
41+
42+
private final RepositoryMetadata repositoryMetadata;
43+
44+
private final ValueExpressionDelegate valueExpressions;
45+
46+
private final ProjectionFactory projectionFactory;
47+
48+
private final ConcurrentLruCache<DeclaredQuery, QueryEnhancer> enhancers;
49+
50+
private final ConcurrentLruCache<String, ValueExpression> expressions;
51+
52+
private final ConcurrentLruCache<Method, ValueEvaluationContextProvider> contextProviders;
53+
54+
protected AotRepositoryFragmentSupport(QueryEnhancerSelector selector,
55+
RepositoryFactoryBeanSupport.FragmentCreationContext context) {
56+
this(selector, context.getRepositoryMetadata(), context.getValueExpressionDelegate(),
57+
context.getProjectionFactory());
58+
}
59+
60+
protected AotRepositoryFragmentSupport(QueryEnhancerSelector selector, RepositoryMetadata repositoryMetadata,
61+
ValueExpressionDelegate valueExpressions, ProjectionFactory projectionFactory) {
62+
63+
this.repositoryMetadata = repositoryMetadata;
64+
this.valueExpressions = valueExpressions;
65+
this.projectionFactory = projectionFactory;
66+
this.enhancers = new ConcurrentLruCache<>(32, query -> selector.select(query).create(query));
67+
this.expressions = new ConcurrentLruCache<>(32, valueExpressions::parse);
68+
this.contextProviders = new ConcurrentLruCache<>(32, it -> valueExpressions
69+
.createValueContextProvider(new JpaParameters(ParametersSource.of(repositoryMetadata, it))));
70+
}
71+
72+
/**
73+
* Rewrite a {@link DeclaredQuery} to apply {@link Sort} and {@link Class} projection.
74+
*
75+
* @param query
76+
* @param sort
77+
* @param returnedType
78+
* @return
79+
*/
80+
protected String rewriteQuery(DeclaredQuery query, Sort sort, Class<?> returnedType) {
81+
82+
QueryEnhancer queryStringEnhancer = this.enhancers.get(query);
83+
return queryStringEnhancer.rewrite(new DefaultQueryRewriteInformation(sort,
84+
ReturnedType.of(returnedType, repositoryMetadata.getDomainType(), projectionFactory)));
85+
}
86+
87+
/**
88+
* Evaluate a Value Expression.
89+
*
90+
* @param method
91+
* @param expressionString
92+
* @param args
93+
* @return
94+
*/
95+
protected @Nullable Object evaluateExpression(Method method, String expressionString, Object... args) {
96+
97+
ValueExpression expression = this.expressions.get(expressionString);
98+
ValueEvaluationContextProvider contextProvider = this.contextProviders.get(method);
99+
100+
return expression.evaluate(contextProvider.getEvaluationContext(args, expression.getExpressionDependencies()));
101+
}
102+
103+
private record DefaultQueryRewriteInformation(Sort sort,
104+
ReturnedType returnedType) implements QueryEnhancer.QueryRewriteInformation {
105+
106+
@Override
107+
public Sort getSort() {
108+
return sort();
109+
}
110+
111+
@Override
112+
public ReturnedType getReturnedType() {
113+
return returnedType();
114+
}
115+
116+
}
117+
118+
}

0 commit comments

Comments
 (0)