Skip to content

Commit f793c9e

Browse files
Move query rendering to dedicated TokenRenderer.
This commit introduces a QueryTokenStream to reduce the number of stream and collect operations to estimate the size before iterating entries. See: #3309
1 parent 2dc94dd commit f793c9e

23 files changed

+424
-187
lines changed

spring-data-jpa-performance/src/main/java/org/springframework/data/jpa/repository/PersonRepository.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,9 @@ public interface PersonRepository extends ListCrudRepository<Person, Integer> {
3939

4040
@Query(value = "SELECT * FROM person WHERE firstname = ?1", nativeQuery = true)
4141
List<Person> findAllWithNativeQueryByFirstname(String firstname);
42+
43+
Long countByFirstname(String firstname);
44+
45+
@Query("SELECT COUNT(*) FROM org.springframework.data.jpa.model.Person p WHERE p.firstname = ?1")
46+
Long countWithAnnotatedQueryByFirstname(String firstname);
4247
}

spring-data-jpa-performance/src/test/java/org/springframework/data/jpa/repository/RepositoryFinderTests.java

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@
5858
@Timeout(time = 2)
5959
public class RepositoryFinderTests {
6060

61+
private static final String PERSON_FIRSTNAME = "first";
62+
private static final String COLUMN_PERSON_FIRSTNAME = "firstname";
63+
6164
@State(Scope.Benchmark)
6265
public static class BenchmarkParameters {
6366

@@ -83,7 +86,7 @@ public void doSetup() {
8386
entityManager.persist(generalProfile);
8487
entityManager.persist(sdUserProfile);
8588

86-
Person person = new Person("first", "last");
89+
Person person = new Person(PERSON_FIRSTNAME, "last");
8790
person.setProfiles(Set.of(generalProfile, sdUserProfile));
8891
entityManager.persist(person);
8992
entityManager.getTransaction().commit();
@@ -128,7 +131,7 @@ public List<Person> baselineEntityManagerCriteriaQuery(BenchmarkParameters param
128131
CriteriaQuery<Person> query = criteriaBuilder.createQuery(Person.class);
129132
Root<Person> root = query.from(Person.class);
130133
TypedQuery<Person> typedQuery = parameters.entityManager
131-
.createQuery(query.where(criteriaBuilder.equal(root.get("firstname"), "first")));
134+
.createQuery(query.where(criteriaBuilder.equal(root.get(COLUMN_PERSON_FIRSTNAME), PERSON_FIRSTNAME)));
132135

133136
return typedQuery.getResultList();
134137
}
@@ -138,35 +141,52 @@ public List<Person> baselineEntityManagerHQLQuery(BenchmarkParameters parameters
138141

139142
Query query = parameters.entityManager
140143
.createQuery("SELECT p FROM org.springframework.data.jpa.model.Person p WHERE p.firstname = ?1");
141-
query.setParameter(1, "first");
144+
query.setParameter(1, PERSON_FIRSTNAME);
142145

143146
return query.getResultList();
144147
}
145148

149+
@Benchmark
150+
public Long baselineEntityManagerCount(BenchmarkParameters parameters) {
151+
152+
Query query = parameters.entityManager.createQuery("SELECT COUNT(*) FROM org.springframework.data.jpa.model.Person p WHERE p.firstname = ?1");
153+
query.setParameter(1, PERSON_FIRSTNAME);
154+
155+
return (Long) query.getSingleResult();
156+
}
157+
146158
@Benchmark
147159
public List<Person> derivedFinderMethod(BenchmarkParameters parameters) {
148-
return parameters.repositoryProxy.findAllByFirstname("first");
160+
return parameters.repositoryProxy.findAllByFirstname(PERSON_FIRSTNAME);
149161
}
150162

151163
@Benchmark
152164
public List<IPersonProjection> derivedFinderMethodWithInterfaceProjection(BenchmarkParameters parameters) {
153-
return parameters.repositoryProxy.findAllAndProjectToInterfaceByFirstname("first");
165+
return parameters.repositoryProxy.findAllAndProjectToInterfaceByFirstname(PERSON_FIRSTNAME);
154166
}
155167

156-
157168
@Benchmark
158169
public List<Person> stringBasedQuery(BenchmarkParameters parameters) {
159-
return parameters.repositoryProxy.findAllWithAnnotatedQueryByFirstname("first");
170+
return parameters.repositoryProxy.findAllWithAnnotatedQueryByFirstname(PERSON_FIRSTNAME);
160171
}
161172

162173
@Benchmark
163174
public List<Person> stringBasedQueryDynamicSort(BenchmarkParameters parameters) {
164-
return parameters.repositoryProxy.findAllWithAnnotatedQueryByFirstname("first", Sort.by("firstname"));
175+
return parameters.repositoryProxy.findAllWithAnnotatedQueryByFirstname(PERSON_FIRSTNAME, Sort.by(COLUMN_PERSON_FIRSTNAME));
165176
}
166177

167178
@Benchmark
168179
public List<Person> stringBasedNativeQuery(BenchmarkParameters parameters) {
169-
return parameters.repositoryProxy.findAllWithNativeQueryByFirstname("first");
180+
return parameters.repositoryProxy.findAllWithNativeQueryByFirstname(PERSON_FIRSTNAME);
181+
}
182+
183+
@Benchmark
184+
public Long derivedCount(BenchmarkParameters parameters) {
185+
return parameters.repositoryProxy.countByFirstname(PERSON_FIRSTNAME);
170186
}
171187

188+
@Benchmark
189+
public Long stringBasedCount(BenchmarkParameters parameters) {
190+
return parameters.repositoryProxy.countWithAnnotatedQueryByFirstname(PERSON_FIRSTNAME);
191+
}
172192
}

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

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,8 @@
1717

1818
import static org.springframework.data.jpa.repository.query.JpaQueryParsingToken.*;
1919

20-
import java.util.List;
21-
2220
import org.springframework.data.jpa.repository.query.QueryRenderer.QueryRendererBuilder;
21+
import org.springframework.data.jpa.repository.query.QueryTransformers.CountSelectionTokenStream;
2322
import org.springframework.lang.Nullable;
2423

2524
/**
@@ -87,10 +86,10 @@ public QueryRendererBuilder visitSelect_clause(EqlParser.Select_clauseContext ct
8786
QueryRendererBuilder selectionListbuilder = QueryRendererBuilder.concat(ctx.select_item(), this::visit,
8887
TOKEN_COMMA);
8988

90-
List<JpaQueryParsingToken> countSelection = QueryTransformers
91-
.filterCountSelection(selectionListbuilder.build().stream().toList());
89+
CountSelectionTokenStream countSelection = QueryTransformers
90+
.filterCountSelection(selectionListbuilder);
9291

93-
if (countSelection.stream().anyMatch(eqlToken -> eqlToken.getToken().contains("new"))) {
92+
if (countSelection.requiresPrimaryAlias()) {
9493
// constructor
9594
nested.append(new JpaQueryParsingToken(primaryFromAlias));
9695
} else {

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

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,7 @@ public QueryRendererBuilder visitSelect_item(EqlParser.Select_itemContext ctx) {
111111
QueryRendererBuilder builder = super.visitSelect_item(ctx);
112112

113113
if (ctx.result_variable() != null) {
114-
List<JpaQueryParsingToken> tokens = builder.build().stream().toList();
115-
transformerSupport.registerAlias(tokens.get(tokens.size() - 1).getToken());
114+
transformerSupport.registerAlias(builder.lastToken());
116115
}
117116

118117
return builder;
@@ -122,9 +121,7 @@ public QueryRendererBuilder visitSelect_item(EqlParser.Select_itemContext ctx) {
122121
public QueryRendererBuilder visitJoin(EqlParser.JoinContext ctx) {
123122

124123
QueryRendererBuilder builder = super.visitJoin(ctx);
125-
126-
List<JpaQueryParsingToken> tokens = builder.build().stream().toList();
127-
transformerSupport.registerAlias(tokens.get(tokens.size() - 1).getToken());
124+
transformerSupport.registerAlias(builder.lastToken());
128125

129126
return builder;
130127
}

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

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,8 @@
1717

1818
import static org.springframework.data.jpa.repository.query.JpaQueryParsingToken.*;
1919

20-
import java.util.List;
21-
2220
import org.springframework.data.jpa.repository.query.QueryRenderer.QueryRendererBuilder;
21+
import org.springframework.data.jpa.repository.query.QueryTransformers.CountSelectionTokenStream;
2322
import org.springframework.lang.Nullable;
2423

2524
/**
@@ -196,10 +195,10 @@ public QueryRendererBuilder visitSelectClause(HqlParser.SelectClauseContext ctx)
196195

197196
if (ctx.DISTINCT() != null) {
198197

199-
List<JpaQueryParsingToken> countSelection = QueryTransformers
200-
.filterCountSelection(selectionListbuilder.build().stream().toList());
198+
CountSelectionTokenStream countSelection = QueryTransformers
199+
.filterCountSelection(selectionListbuilder);
201200

202-
if (countSelection.stream().anyMatch(hqlToken -> hqlToken.getToken().contains("new"))) {
201+
if (countSelection.requiresPrimaryAlias()) {
203202
// constructor
204203
nested.append(new JpaQueryParsingToken(primaryFromAlias));
205204
} else {

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2291,21 +2291,21 @@ public QueryRendererBuilder visitCollectionExpression(HqlParser.CollectionExpres
22912291

22922292
if (ctx.IS() != null) {
22932293

2294-
builder.append(JpaQueryExpression.expression(ctx.IS()));
2294+
builder.append(JpaExpressionToken.expression(ctx.IS()));
22952295

22962296
if (ctx.NOT() != null) {
22972297
builder.append(JpaQueryParsingToken.expression(ctx.NOT()));
22982298
}
22992299

2300-
builder.append(JpaQueryExpression.expression(ctx.EMPTY()));
2300+
builder.append(JpaExpressionToken.expression(ctx.EMPTY()));
23012301
} else if (ctx.MEMBER() != null) {
23022302

23032303
if (ctx.NOT() != null) {
23042304
builder.append(JpaQueryParsingToken.expression(ctx.NOT()));
23052305
}
23062306

2307-
builder.append(JpaQueryExpression.expression(ctx.MEMBER()));
2308-
builder.append(JpaQueryExpression.expression(ctx.OF()));
2307+
builder.append(JpaExpressionToken.expression(ctx.MEMBER()));
2308+
builder.append(JpaExpressionToken.expression(ctx.OF()));
23092309
builder.append(visit(ctx.path()));
23102310
}
23112311

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

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,7 @@ public QueryRendererBuilder visitJoinPath(HqlParser.JoinPathContext ctx) {
101101
QueryRendererBuilder builder = super.visitJoinPath(ctx);
102102

103103
if (ctx.variable() != null) {
104-
List<JpaQueryParsingToken> tokens = builder.build().stream().toList();
105-
transformerSupport.registerAlias(tokens.get(tokens.size() - 1).getToken());
104+
transformerSupport.registerAlias(builder.lastToken());
106105
}
107106

108107
return builder;
@@ -114,8 +113,7 @@ public QueryRendererBuilder visitJoinSubquery(HqlParser.JoinSubqueryContext ctx)
114113
QueryRendererBuilder builder = super.visitJoinSubquery(ctx);
115114

116115
if (ctx.variable() != null) {
117-
List<JpaQueryParsingToken> tokens = builder.build().stream().toList();
118-
transformerSupport.registerAlias(tokens.get(tokens.size() - 1).getToken());
116+
transformerSupport.registerAlias(builder.lastToken());
119117
}
120118

121119
return builder;
@@ -127,8 +125,7 @@ public QueryRendererBuilder visitVariable(HqlParser.VariableContext ctx) {
127125
QueryRendererBuilder builder = super.visitVariable(ctx);
128126

129127
if (ctx.identifier() != null) {
130-
List<JpaQueryParsingToken> tokens = builder.build().stream().toList();
131-
transformerSupport.registerAlias(tokens.get(tokens.size() - 1).getToken());
128+
transformerSupport.registerAlias(builder.lastToken());
132129
}
133130

134131
return builder;

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

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
*/
1616
package org.springframework.data.jpa.repository.query;
1717

18-
import static org.springframework.data.jpa.repository.query.JpaQueryParsingToken.*;
19-
2018
import java.util.List;
2119
import java.util.Set;
2220
import java.util.function.BiFunction;
@@ -31,7 +29,6 @@
3129
import org.antlr.v4.runtime.TokenStream;
3230
import org.antlr.v4.runtime.atn.PredictionMode;
3331
import org.antlr.v4.runtime.tree.ParseTreeVisitor;
34-
3532
import org.springframework.data.domain.Sort;
3633
import org.springframework.lang.Nullable;
3734
import org.springframework.util.Assert;
@@ -65,7 +62,7 @@ class JpaQueryEnhancer implements QueryEnhancer {
6562
this.introspector.visit(context);
6663

6764
List<JpaQueryParsingToken> tokens = introspector.getProjection();
68-
this.projection = tokens.isEmpty() ? "" : render(tokens);
65+
this.projection = tokens.isEmpty() ? "" : QueryRenderer.TokenRenderer.render(tokens);
6966
}
7067

7168
static <P extends Parser> ParserRuleContext parse(String query, Function<CharStream, Lexer> lexerFactoryFunction,
@@ -194,7 +191,7 @@ public DeclaredQuery getQuery() {
194191
*/
195192
@Override
196193
public String applySorting(Sort sort) {
197-
return render(sortFunction.apply(sort, detectAlias()).visit(context));
194+
return QueryRenderer.TokenRenderer.render(sortFunction.apply(sort, detectAlias()).visit(context));
198195
}
199196

200197
/**
@@ -226,7 +223,7 @@ public String createCountQueryFor() {
226223
*/
227224
@Override
228225
public String createCountQueryFor(@Nullable String countProjection) {
229-
return render(countQueryFunction.apply(countProjection, detectAlias()).visit(context));
226+
return QueryRenderer.TokenRenderer.render(countQueryFunction.apply(countProjection, detectAlias()).visit(context));
230227
}
231228

232229
/**

0 commit comments

Comments
 (0)