Skip to content

Commit 3ab9e29

Browse files
Cache string query after processing sort
1 parent c8244be commit 3ab9e29

File tree

2 files changed

+96
-12
lines changed

2 files changed

+96
-12
lines changed

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

+79-9
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
import jakarta.persistence.EntityManager;
1919
import jakarta.persistence.Query;
2020

21+
import java.util.Objects;
22+
2123
import org.springframework.data.domain.Pageable;
2224
import org.springframework.data.domain.Sort;
2325
import org.springframework.data.jpa.repository.QueryRewriter;
@@ -28,6 +30,7 @@
2830
import org.springframework.expression.spel.standard.SpelExpressionParser;
2931
import org.springframework.lang.Nullable;
3032
import org.springframework.util.Assert;
33+
import org.springframework.util.ConcurrentLruCache;
3134

3235
/**
3336
* Base class for {@link String} based JPA queries.
@@ -40,6 +43,7 @@
4043
* @author Mark Paluch
4144
* @author Diego Krupitza
4245
* @author Greg Turnquist
46+
* @author Christoph Strobl
4347
*/
4448
abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
4549

@@ -49,6 +53,7 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
4953
private final SpelExpressionParser parser;
5054
private final QueryParameterSetter.QueryMetadataCache metadataCache = new QueryParameterSetter.QueryMetadataCache();
5155
private final QueryRewriter queryRewriter;
56+
private ConcurrentLruCache<CachableQuery, String> queryCache = new ConcurrentLruCache<>(100, this::applySorting);
5257

5358
/**
5459
* Creates a new {@link AbstractStringBasedJpaQuery} from the given {@link JpaQueryMethod}, {@link EntityManager} and
@@ -106,16 +111,8 @@ public Query doCreateQuery(JpaParametersParameterAccessor accessor) {
106111
return parameterBinder.get().bindAndPrepare(query, metadata, accessor);
107112
}
108113

109-
private String applySortingIfNecessary(DeclaredQuery query, Sort sort) {
110-
111-
if (sort.isUnsorted()) {
112-
return query.getQueryString();
113-
}
114-
return applySorting(query, sort);
115-
}
116-
117114
protected String applySorting(DeclaredQuery query, Sort sort) {
118-
return QueryEnhancerFactory.forQuery(query).applySorting(sort, query.getAlias());
115+
return queryCache.get(new CachableQuery(query, sort));
119116
}
120117

121118
@Override
@@ -191,4 +188,77 @@ protected String potentiallyRewriteQuery(String originalQuery, Sort sort, @Nulla
191188
? queryRewriter.rewrite(originalQuery, pageable) //
192189
: queryRewriter.rewrite(originalQuery, sort);
193190
}
191+
192+
String applySorting(CachableQuery cachableQuery) {
193+
194+
return QueryEnhancerFactory.forQuery(cachableQuery.getDeclaredQuery()).applySorting(cachableQuery.getSort(),
195+
cachableQuery.getAlias());
196+
}
197+
198+
private String applySortingIfNecessary(DeclaredQuery query, Sort sort) {
199+
200+
if (sort.isUnsorted()) {
201+
return query.getQueryString();
202+
}
203+
return applySorting(query, sort);
204+
}
205+
206+
/**
207+
* Value object with optimized {@link Object#equals(Object)} to cache a query based on its query string and
208+
* {@link Sort sorting}.
209+
*
210+
* @since 3.2.3
211+
* @author Christoph Strobl
212+
*/
213+
static class CachableQuery {
214+
215+
private DeclaredQuery declaredQuery;
216+
private final String queryString;
217+
private final Sort sort;
218+
219+
CachableQuery(DeclaredQuery query, Sort sort) {
220+
221+
this.declaredQuery = query;
222+
this.queryString = query.getQueryString();
223+
this.sort = sort;
224+
}
225+
226+
DeclaredQuery getDeclaredQuery() {
227+
return declaredQuery;
228+
}
229+
230+
Sort getSort() {
231+
return sort;
232+
}
233+
234+
String getAlias() {
235+
return declaredQuery.getAlias();
236+
}
237+
238+
@Override
239+
public boolean equals(Object o) {
240+
241+
if (this == o) {
242+
return true;
243+
}
244+
if (o == null || getClass() != o.getClass()) {
245+
return false;
246+
}
247+
248+
CachableQuery that = (CachableQuery) o;
249+
250+
if (!Objects.equals(queryString, that.queryString)) {
251+
return false;
252+
}
253+
return Objects.equals(sort, that.sort);
254+
}
255+
256+
@Override
257+
public int hashCode() {
258+
259+
int result = queryString != null ? queryString.hashCode() : 0;
260+
result = 31 * result + (sort != null ? sort.hashCode() : 0);
261+
return result;
262+
}
263+
}
194264
}

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

+17-3
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,20 @@ void shouldAppendSortIfSortPresent() {
8080
stringQuery.called("applySorting").times(1);
8181
}
8282

83+
@Test // GH-3311
84+
void cachesInvocationBasedOnSortArgument() {
85+
86+
InvocationCapturingStringQueryStub stringQuery = forMethod(TestRepo.class, "find", Sort.class);
87+
stringQuery.createQueryWithArguments(Sort.by("name"));
88+
stringQuery.called("applySorting").times(1);
89+
90+
stringQuery.createQueryWithArguments(Sort.by("name"));
91+
stringQuery.called("applySorting").times(1);
92+
93+
stringQuery.createQueryWithArguments(Sort.by("age"));
94+
stringQuery.called("applySorting").times(2);
95+
}
96+
8397
interface TestRepo extends Repository<Object, Object> {
8498

8599
@Query("SELECT e FROM Employee e")
@@ -132,11 +146,11 @@ public EntityManager get() {
132146
}
133147

134148
@Override
135-
protected String applySorting(DeclaredQuery query, Sort sort) {
149+
protected String applySorting(CachableQuery query) {
136150

137-
captureInvocation("applySorting", query, sort);
151+
captureInvocation("applySorting", query);
138152

139-
return super.applySorting(query, sort);
153+
return super.applySorting(query);
140154
}
141155

142156
@Override

0 commit comments

Comments
 (0)