Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit f027352

Browse files
committedSep 20, 2024··
Hacking: Caching
1 parent 1bdffd6 commit f027352

File tree

10 files changed

+196
-59
lines changed

10 files changed

+196
-59
lines changed
 

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ class HibernateJpaParametersParameterAccessor extends JpaParametersParameterAcce
5151
* @param values must not be {@literal null}.
5252
* @param em must not be {@literal null}.
5353
*/
54-
HibernateJpaParametersParameterAccessor(Parameters<?, ?> parameters, Object[] values, EntityManager em) {
54+
HibernateJpaParametersParameterAccessor(JpaParameters parameters, Object[] values, EntityManager em) {
5555

5656
super(parameters, values);
5757

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,21 @@
3131
*/
3232
public class JpaParametersParameterAccessor extends ParametersParameterAccessor {
3333

34+
private final JpaParameters parameters;
35+
3436
/**
3537
* Creates a new {@link ParametersParameterAccessor}.
3638
*
3739
* @param parameters must not be {@literal null}.
3840
* @param values must not be {@literal null}.
3941
*/
40-
public JpaParametersParameterAccessor(Parameters<?, ?> parameters, Object[] values) {
42+
public JpaParametersParameterAccessor(JpaParameters parameters, Object[] values) {
4143
super(parameters, values);
44+
this.parameters = parameters;
45+
}
46+
47+
public JpaParameters getParameters() {
48+
return parameters;
4249
}
4350

4451
@Nullable

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

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434
import org.springframework.data.jpa.domain.JpaSort;
3535
import org.springframework.data.jpa.repository.query.JpqlQueryBuilder.PathAndOrigin;
3636
import org.springframework.data.jpa.repository.query.ParameterBinding.PartTreeParameterBinding;
37-
import org.springframework.data.jpa.repository.query.ParameterMetadataProvider.ParameterMetadata;
3837
import org.springframework.data.jpa.repository.support.JpqlQueryTemplates;
3938
import org.springframework.data.mapping.PropertyPath;
4039
import org.springframework.data.mapping.PropertyReferenceException;
@@ -58,7 +57,7 @@
5857
* @author Andrey Kovalev
5958
* @author Greg Turnquist
6059
*/
61-
class JpaQueryCreator extends AbstractQueryCreator<String, JpqlQueryBuilder.Predicate> {
60+
class JpaQueryCreator extends AbstractQueryCreator<String, JpqlQueryBuilder.Predicate> implements JpqlQueryCreator {
6261

6362
private final ReturnedType returnedType;
6463
private final ParameterMetadataProvider provider;
@@ -113,6 +112,11 @@ public List<ParameterBinding> getBindings() {
113112
return provider.getBindings();
114113
}
115114

115+
@Override
116+
public ParameterBinder getBinder() {
117+
return ParameterBinderFactory.createBinder(provider.getParameters(), getBindings());
118+
}
119+
116120
@Override
117121
protected JpqlQueryBuilder.Predicate create(Part part, Iterator<Object> iterator) {
118122
return toPredicate(part);
@@ -258,10 +262,6 @@ String render(int position) {
258262
return "?" + position;
259263
}
260264

261-
private String render(ParameterMetadata metadata) {
262-
return render(metadata.getPosition());
263-
}
264-
265265
/**
266266
* Creates a {@link Predicate} from the given {@link Part}.
267267
*
@@ -278,7 +278,6 @@ private JpqlQueryBuilder.Predicate toPredicate(Part part) {
278278
* @author Phil Webb
279279
* @author Oliver Gierke
280280
*/
281-
@SuppressWarnings({ "rawtypes" })
282281
private class PredicateBuilder {
283282

284283
private final Part part;
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2024 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 java.util.List;
19+
20+
import org.springframework.data.domain.Sort;
21+
22+
/**
23+
* @author Mark Paluch
24+
*/
25+
interface JpqlQueryCreator {
26+
27+
boolean useTupleQuery();
28+
29+
String createQuery(Sort sort);
30+
31+
List<ParameterBinding> getBindings();
32+
33+
ParameterBinder getBinder();
34+
}

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

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -62,54 +62,52 @@ class ParameterMetadataProvider {
6262
private final @Nullable Iterator<Object> bindableParameterValues;
6363
private final EscapeCharacter escape;
6464
private final JpqlQueryTemplates templates;
65+
private final JpaParameters jpaParameters;
6566
private int position;
6667

6768
/**
6869
* Creates a new {@link ParameterMetadataProvider} from the given {@link CriteriaBuilder} and
6970
* {@link ParametersParameterAccessor}.
7071
*
71-
* @param builder must not be {@literal null}.
7272
* @param accessor must not be {@literal null}.
7373
* @param escape must not be {@literal null}.
7474
* @param templates must not be {@literal null}.
7575
*/
76-
public ParameterMetadataProvider(CriteriaBuilder builder, ParametersParameterAccessor accessor,
76+
public ParameterMetadataProvider(JpaParametersParameterAccessor accessor,
7777
EscapeCharacter escape, JpqlQueryTemplates templates) {
78-
this(builder, accessor.iterator(), accessor.getParameters(), escape, templates);
78+
this(accessor.iterator(), accessor.getParameters(), escape, templates);
7979
}
8080

8181
/**
8282
* Creates a new {@link ParameterMetadataProvider} from the given {@link CriteriaBuilder} and {@link Parameters} with
8383
* support for parameter value customizations via {@link PersistenceProvider}.
8484
*
85-
* @param builder must not be {@literal null}.
8685
* @param parameters must not be {@literal null}.
8786
* @param escape must not be {@literal null}.
8887
* @param templates must not be {@literal null}.
8988
*/
90-
public ParameterMetadataProvider(CriteriaBuilder builder, Parameters<?, ?> parameters, EscapeCharacter escape,
89+
public ParameterMetadataProvider(JpaParameters parameters, EscapeCharacter escape,
9190
JpqlQueryTemplates templates) {
92-
this(builder, null, parameters, escape, templates);
91+
this(null, parameters, escape, templates);
9392
}
9493

9594
/**
9695
* Creates a new {@link ParameterMetadataProvider} from the given {@link CriteriaBuilder} an {@link Iterable} of all
9796
* bindable parameter values, and {@link Parameters}.
9897
*
99-
* @param builder must not be {@literal null}.
10098
* @param bindableParameterValues may be {@literal null}.
10199
* @param parameters must not be {@literal null}.
102100
* @param escape must not be {@literal null}.
103101
* @param templates must not be {@literal null}.
104102
*/
105-
private ParameterMetadataProvider(CriteriaBuilder builder, @Nullable Iterator<Object> bindableParameterValues,
106-
Parameters<?, ?> parameters, EscapeCharacter escape, JpqlQueryTemplates templates) {
103+
private ParameterMetadataProvider(@Nullable Iterator<Object> bindableParameterValues, JpaParameters parameters,
104+
EscapeCharacter escape, JpqlQueryTemplates templates) {
107105

108-
Assert.notNull(builder, "CriteriaBuilder must not be null");
109106
Assert.notNull(parameters, "Parameters must not be null");
110107
Assert.notNull(escape, "EscapeCharacter must not be null");
111108
Assert.notNull(templates, "JpqlQueryTemplates must not be null");
112109

110+
this.jpaParameters = parameters;
113111
this.parameters = parameters.getBindableParameters().iterator();
114112
this.bindings = new ArrayList<>();
115113
this.bindableParameterValues = bindableParameterValues;
@@ -207,6 +205,10 @@ public ParameterBinding nextSynthetic(Object value, Object source) {
207205
return new ParameterBinding(BindingIdentifier.of(currentPosition), ParameterOrigin.synthetic(value, source));
208206
}
209207

208+
public JpaParameters getParameters() {
209+
return this.jpaParameters;
210+
}
211+
210212
/**
211213
* @author Oliver Gierke
212214
* @author Thomas Darimont

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

Lines changed: 130 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,12 @@
2020
import jakarta.persistence.Query;
2121
import jakarta.persistence.Tuple;
2222
import jakarta.persistence.TypedQuery;
23-
import jakarta.persistence.criteria.CriteriaBuilder;
2423
import jakarta.persistence.criteria.CriteriaQuery;
2524

25+
import java.util.LinkedHashMap;
26+
import java.util.List;
27+
import java.util.Map;
28+
2629
import org.springframework.data.domain.KeysetScrollPosition;
2730
import org.springframework.data.domain.OffsetScrollPosition;
2831
import org.springframework.data.domain.ScrollPosition;
@@ -40,6 +43,7 @@
4043
import org.springframework.data.repository.query.parser.PartTree;
4144
import org.springframework.data.util.Streamable;
4245
import org.springframework.lang.Nullable;
46+
import org.springframework.util.Assert;
4347

4448
/**
4549
* A {@link AbstractJpaQuery} implementation based on a {@link PartTree}.
@@ -197,6 +201,7 @@ private static boolean expectsCollection(Type type) {
197201
return type == Type.IN || type == Type.NOT_IN;
198202
}
199203

204+
200205
/**
201206
* Query preparer to create {@link CriteriaQuery} instances and potentially cache them.
202207
*
@@ -205,14 +210,21 @@ private static boolean expectsCollection(Type type) {
205210
*/
206211
private class QueryPreparer {
207212

213+
private final Map<Sort, JpqlQueryCreator> cache = new LinkedHashMap<Sort, JpqlQueryCreator>() {
214+
@Override
215+
protected boolean removeEldestEntry(Map.Entry<Sort, JpqlQueryCreator> eldest) {
216+
return size() > 256;
217+
}
218+
};
219+
208220
/**
209221
* Creates a new {@link Query} for the given parameter values.
210222
*/
211223
public Query createQuery(JpaParametersParameterAccessor accessor) {
212224

213-
JpaQueryCreator creator = createCreator(accessor);
214-
215-
String jpql = creator.createQuery(getDynamicSort(accessor));
225+
Sort sort = getDynamicSort(accessor);
226+
JpqlQueryCreator creator = createCreator(sort, accessor);
227+
String jpql = creator.createQuery(sort);
216228
Query query;
217229

218230
try {
@@ -221,7 +233,7 @@ public Query createQuery(JpaParametersParameterAccessor accessor) {
221233
throw new BadJpqlGrammarException(e.getMessage(), jpql, e);
222234
}
223235

224-
ParameterBinder binder = ParameterBinderFactory.createBinder(parameters, creator.getBindings());
236+
ParameterBinder binder = creator.getBinder();
225237

226238
ScrollPosition scrollPosition = accessor.getParameters().hasScrollPositionParameter()
227239
? accessor.getScrollPosition()
@@ -264,30 +276,78 @@ private Query restrictMaxResultsIfNecessary(Query query, @Nullable ScrollPositio
264276
return query;
265277
}
266278

267-
protected JpaQueryCreator createCreator(@Nullable JpaParametersParameterAccessor accessor) {
279+
protected JpqlQueryCreator createCreator(Sort sort, JpaParametersParameterAccessor accessor) {
268280

269-
EntityManager entityManager = getEntityManager();
281+
synchronized (cache) {
282+
JpqlQueryCreator jpqlQueryCreator = cache.get(sort);
283+
if (jpqlQueryCreator != null) {
284+
return jpqlQueryCreator;
285+
}
286+
}
270287

271-
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
288+
EntityManager entityManager = getEntityManager();
272289
ResultProcessor processor = getQueryMethod().getResultProcessor();
273290

274-
ParameterMetadataProvider provider;
275-
ReturnedType returnedType;
276-
277-
if (accessor != null) {
278-
provider = new ParameterMetadataProvider(builder, accessor, escape, templates);
279-
returnedType = processor.withDynamicProjection(accessor).getReturnedType();
280-
} else {
281-
provider = new ParameterMetadataProvider(builder, parameters, escape, templates);
282-
returnedType = processor.getReturnedType();
283-
}
291+
ParameterMetadataProvider provider = new ParameterMetadataProvider(accessor, escape, templates);
292+
ReturnedType returnedType = processor.withDynamicProjection(accessor).getReturnedType();
284293

285-
if (accessor != null && accessor.getScrollPosition() instanceof KeysetScrollPosition keyset) {
294+
if (accessor.getScrollPosition() instanceof KeysetScrollPosition keyset) {
286295
return new JpaKeysetScrollQueryCreator(tree, returnedType, provider, templates, entityInformation, keyset,
287296
entityManager);
288297
}
289298

290-
return new JpaQueryCreator(tree, returnedType, provider, templates, em);
299+
JpqlQueryCreator creator = new CacheableJpqlQueryCreator(sort,
300+
new JpaQueryCreator(tree, returnedType, provider, templates, em));
301+
302+
if (accessor.getParameters().hasDynamicProjection()) {
303+
return creator;
304+
}
305+
306+
synchronized (cache) {
307+
cache.put(sort, creator);
308+
}
309+
310+
return creator;
311+
}
312+
313+
static class CacheableJpqlQueryCreator implements JpqlQueryCreator {
314+
315+
private final Sort expectedSort;
316+
private final String query;
317+
private final boolean useTupleQuery;
318+
private final List<ParameterBinding> parameterBindings;
319+
private final ParameterBinder binder;
320+
321+
public CacheableJpqlQueryCreator(Sort expectedSort, JpqlQueryCreator delegate) {
322+
323+
this.expectedSort = expectedSort;
324+
this.query = delegate.createQuery(expectedSort);
325+
this.useTupleQuery = delegate.useTupleQuery();
326+
this.parameterBindings = delegate.getBindings();
327+
this.binder = delegate.getBinder();
328+
}
329+
330+
@Override
331+
public boolean useTupleQuery() {
332+
return useTupleQuery;
333+
}
334+
335+
@Override
336+
public String createQuery(Sort sort) {
337+
338+
Assert.isTrue(sort.equals(expectedSort), "Expected sort does not match");
339+
return query;
340+
}
341+
342+
@Override
343+
public List<ParameterBinding> getBindings() {
344+
return parameterBindings;
345+
}
346+
347+
@Override
348+
public ParameterBinder getBinder() {
349+
return binder;
350+
}
291351
}
292352

293353
/**
@@ -313,22 +373,26 @@ private Sort getDynamicSort(JpaParametersParameterAccessor accessor) {
313373
*/
314374
private class CountQueryPreparer extends QueryPreparer {
315375

376+
private volatile JpqlQueryCreator cached;
377+
316378
@Override
317-
protected JpaQueryCreator createCreator(@Nullable JpaParametersParameterAccessor accessor) {
379+
protected JpqlQueryCreator createCreator(Sort sort, JpaParametersParameterAccessor accessor) {
318380

319-
EntityManager entityManager = getEntityManager();
320-
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
381+
JpqlQueryCreator cached = this.cached;
382+
383+
if (cached != null) {
384+
return cached;
385+
}
321386

322-
ParameterMetadataProvider provider;
387+
ParameterMetadataProvider provider = new ParameterMetadataProvider(accessor, escape, templates);
388+
JpaCountQueryCreator creator = new JpaCountQueryCreator(tree,
389+
getQueryMethod().getResultProcessor().getReturnedType(), provider, templates, em);
323390

324-
if (accessor != null) {
325-
provider = new ParameterMetadataProvider(builder, accessor, escape, templates);
326-
} else {
327-
provider = new ParameterMetadataProvider(builder, parameters, escape, templates);
391+
if (!accessor.getParameters().hasDynamicProjection()) {
392+
return this.cached = new CacheableJpqlCountQueryCreator(creator);
328393
}
329394

330-
return new JpaCountQueryCreator(tree, getQueryMethod().getResultProcessor().getReturnedType(), provider,
331-
templates, em);
395+
return creator;
332396
}
333397

334398
/**
@@ -338,5 +402,41 @@ protected JpaQueryCreator createCreator(@Nullable JpaParametersParameterAccessor
338402
protected Query invokeBinding(ParameterBinder binder, Query query, JpaParametersParameterAccessor accessor) {
339403
return binder.bind(query, accessor);
340404
}
405+
406+
static class CacheableJpqlCountQueryCreator implements JpqlQueryCreator {
407+
408+
private final String query;
409+
private final boolean useTupleQuery;
410+
private final List<ParameterBinding> parameterBindings;
411+
private final ParameterBinder binder;
412+
413+
public CacheableJpqlCountQueryCreator(JpqlQueryCreator delegate) {
414+
415+
this.query = delegate.createQuery(Sort.unsorted());
416+
this.useTupleQuery = delegate.useTupleQuery();
417+
this.parameterBindings = delegate.getBindings();
418+
this.binder = delegate.getBinder();
419+
}
420+
421+
@Override
422+
public boolean useTupleQuery() {
423+
return useTupleQuery;
424+
}
425+
426+
@Override
427+
public String createQuery(Sort sort) {
428+
return query;
429+
}
430+
431+
@Override
432+
public List<ParameterBinding> getBindings() {
433+
return parameterBindings;
434+
}
435+
436+
@Override
437+
public ParameterBinder getBinder() {
438+
return binder;
439+
}
440+
}
341441
}
342442
}

‎spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaCountQueryCreatorIntegrationTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ void distinctFlagOnCountQueryIssuesCountDistinct() throws Exception {
5959
AbstractRepositoryMetadata.getMetadata(SomeRepository.class), new SpelAwareProxyProjectionFactory(), provider);
6060

6161
PartTree tree = new PartTree("findDistinctByRolesIn", User.class);
62-
ParameterMetadataProvider metadataProvider = new ParameterMetadataProvider(entityManager.getCriteriaBuilder(),
62+
ParameterMetadataProvider metadataProvider = new ParameterMetadataProvider(
6363
queryMethod.getParameters(), EscapeCharacter.DEFAULT, JpqlQueryTemplates.UPPER);
6464

6565
JpaCountQueryCreator creator = new JpaCountQueryCreator(tree, queryMethod.getResultProcessor().getReturnedType(),

‎spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaKeysetScrollQueryCreatorTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ void shouldCreateContinuationQuery() throws Exception {
6565
new SpelAwareProxyProjectionFactory(), provider);
6666

6767
PartTree tree = new PartTree("findTop3ByFirstnameStartingWithOrderByFirstnameAscEmailAddressAsc", User.class);
68-
ParameterMetadataProvider metadataProvider = new ParameterMetadataProvider(entityManager.getCriteriaBuilder(),
68+
ParameterMetadataProvider metadataProvider = new ParameterMetadataProvider(
6969
queryMethod.getParameters(), EscapeCharacter.DEFAULT, JpqlQueryTemplates.UPPER);
7070

7171
JpaMetamodelEntityInformation<User, User> entityInformation = new JpaMetamodelEntityInformation<>(User.class,

‎spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/ParameterMetadataProviderIntegrationTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ private ParameterMetadataProvider createProvider(Method method) {
8181
JpaParameters parameters = new JpaParameters(ParametersSource.of(method));
8282
simulateDiscoveredParametername(parameters);
8383

84-
return new ParameterMetadataProvider(em.getCriteriaBuilder(), parameters, EscapeCharacter.DEFAULT,
84+
return new ParameterMetadataProvider(parameters, EscapeCharacter.DEFAULT,
8585
JpqlQueryTemplates.UPPER);
8686
}
8787

‎spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/ParameterMetadataProviderUnitTests.java

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818
import static org.assertj.core.api.Assertions.*;
1919
import static org.mockito.Mockito.*;
2020

21-
import jakarta.persistence.criteria.CriteriaBuilder;
22-
2321
import java.util.Collections;
2422

2523
import org.eclipse.persistence.internal.jpa.querydef.ParameterExpressionImpl;
@@ -32,7 +30,6 @@
3230
import org.mockito.quality.Strictness;
3331

3432
import org.springframework.data.jpa.repository.support.JpqlQueryTemplates;
35-
import org.springframework.data.repository.query.Parameters;
3633
import org.springframework.data.repository.query.parser.Part;
3734

3835
/**
@@ -53,12 +50,10 @@ class ParameterMetadataProviderUnitTests {
5350
@Test // DATAJPA-863
5451
void errorMessageMentionsParametersWhenParametersAreExhausted() {
5552

56-
CriteriaBuilder builder = mock(CriteriaBuilder.class);
57-
58-
Parameters<?, ?> parameters = mock(Parameters.class, RETURNS_DEEP_STUBS);
53+
JpaParameters parameters = mock(JpaParameters.class, RETURNS_DEEP_STUBS);
5954
when(parameters.getBindableParameters().iterator()).thenReturn(Collections.emptyListIterator());
6055

61-
ParameterMetadataProvider metadataProvider = new ParameterMetadataProvider(builder, parameters,
56+
ParameterMetadataProvider metadataProvider = new ParameterMetadataProvider(parameters,
6257
EscapeCharacter.DEFAULT, JpqlQueryTemplates.UPPER);
6358

6459
assertThatExceptionOfType(RuntimeException.class) //

0 commit comments

Comments
 (0)
Please sign in to comment.