Skip to content

Commit 2dc94dd

Browse files
mp911dechristophstrobl
authored andcommitted
Refactor native query handling and JsqlParser usage.
This commit introduce eager alias and projection detection and aims to cache expensive calls. It also revises JPQL parsers and enhancers into single-class hierarchy and removes strange parameter verification (as it was wrong anyway). See: #3309 Closes: #3311
1 parent 3e1c8a2 commit 2dc94dd

29 files changed

+685
-770
lines changed

spring-data-jpa-performance/pom.xml

+7
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,13 @@
8585
<scope>test</scope>
8686
</dependency>
8787

88+
<dependency>
89+
<groupId>com.github.jsqlparser</groupId>
90+
<artifactId>jsqlparser</artifactId>
91+
<version>${jsqlparser}</version>
92+
<scope>test</scope>
93+
</dependency>
94+
8895
</dependencies>
8996

9097
<build>

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

+8-8
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,16 @@
2727
*/
2828
public interface PersonRepository extends ListCrudRepository<Person, Integer> {
2929

30-
List<Person> findAllByFirstname(String firstname);
30+
List<Person> findAllByFirstname(String firstname);
3131

32-
List<IPersonProjection> findAllAndProjectToInterfaceByFirstname(String firstname);
32+
List<IPersonProjection> findAllAndProjectToInterfaceByFirstname(String firstname);
3333

34-
@Query("SELECT p FROM org.springframework.data.jpa.model.Person p WHERE p.firstname = ?1")
35-
List<Person> findAllWithAnnotatedQueryByFirstname(String firstname);
34+
@Query("SELECT p FROM org.springframework.data.jpa.model.Person p WHERE p.firstname = ?1")
35+
List<Person> findAllWithAnnotatedQueryByFirstname(String firstname);
3636

37-
@Query("SELECT p FROM org.springframework.data.jpa.model.Person p WHERE p.firstname = ?1")
38-
List<Person> findAllWithAnnotatedQueryByFirstname(String firstname, Sort sort);
37+
@Query("SELECT p FROM org.springframework.data.jpa.model.Person p WHERE p.firstname = ?1")
38+
List<Person> findAllWithAnnotatedQueryByFirstname(String firstname, Sort sort);
3939

40-
@Query(value = "SELECT * FROM person WHERE firstname = ?1", nativeQuery = true)
41-
List<Person> findAllWithNativeQueryByFirstname(String firstname);
40+
@Query(value = "SELECT * FROM person WHERE firstname = ?1", nativeQuery = true)
41+
List<Person> findAllWithNativeQueryByFirstname(String firstname);
4242
}

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

+16-5
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,37 @@
1515
*/
1616
package org.springframework.data.jpa.repository;
1717

18+
import java.lang.reflect.Method;
19+
1820
import org.springframework.data.domain.Sort;
21+
import org.springframework.data.jpa.provider.PersistenceProvider;
22+
import org.springframework.data.jpa.repository.query.JpaQueryMethod;
23+
import org.springframework.data.jpa.repository.query.StringQuery;
24+
import org.springframework.data.projection.ProjectionFactory;
25+
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
26+
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
1927

2028
/**
2129
* @author Mark Paluch
2230
*/
2331
public class Profiler {
2432

25-
public static void main(String[] args) throws InterruptedException {
33+
public static void main(String[] args) throws Exception {
2634

27-
RepositoryFinderTests tests = new RepositoryFinderTests();
28-
RepositoryFinderTests.BenchmarkParameters params = new RepositoryFinderTests.BenchmarkParameters();
29-
params.doSetup();
35+
DefaultRepositoryMetadata art = new DefaultRepositoryMetadata(PersonRepository.class);
36+
Method method = PersonRepository.class.getMethod("findAllWithAnnotatedQueryByFirstname", String.class, Sort.class);
37+
ProjectionFactory projectionFactory = new SpelAwareProxyProjectionFactory();
3038

3139
System.out.println("Ready. Waiting 10sec");
3240
Thread.sleep(10000);
3341

3442
System.out.println("Go!");
3543

44+
3645
while (true) {
37-
params.repositoryProxy.findAllWithAnnotatedQueryByFirstname("first", Sort.by("firstname"));
46+
47+
JpaQueryMethod queryMethod = new JpaQueryMethod(method, art, projectionFactory, PersistenceProvider.HIBERNATE);
48+
StringQuery stringQuery = new StringQuery(queryMethod.getRequiredAnnotatedQuery(), false);
3849
}
3950

4051
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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 jmh.mbr.junit5.Microbenchmark;
19+
20+
import java.io.IOException;
21+
22+
import org.openjdk.jmh.annotations.Benchmark;
23+
import org.openjdk.jmh.annotations.Fork;
24+
import org.openjdk.jmh.annotations.Level;
25+
import org.openjdk.jmh.annotations.Measurement;
26+
import org.openjdk.jmh.annotations.Scope;
27+
import org.openjdk.jmh.annotations.Setup;
28+
import org.openjdk.jmh.annotations.State;
29+
import org.openjdk.jmh.annotations.Timeout;
30+
import org.openjdk.jmh.annotations.Warmup;
31+
32+
import org.springframework.data.domain.Sort;
33+
34+
/**
35+
* @author Mark Paluch
36+
*/
37+
@Microbenchmark
38+
@Fork(1)
39+
@Warmup(time = 2, iterations = 3)
40+
@Measurement(time = 2)
41+
@Timeout(time = 2)
42+
public class JSqlParserQueryEnhancerTests {
43+
44+
@State(Scope.Benchmark)
45+
public static class BenchmarkParameters {
46+
47+
JSqlParserQueryEnhancer enhancer;
48+
Sort sort = Sort.by("foo");
49+
private byte[] serialized;
50+
51+
@Setup(Level.Iteration)
52+
public void doSetup() throws IOException {
53+
54+
String s = """
55+
select SOME_COLUMN from SOME_TABLE where REPORTING_DATE = :REPORTING_DATE
56+
except
57+
select SOME_COLUMN from SOME_OTHER_TABLE where REPORTING_DATE = :REPORTING_DATE
58+
union select SOME_COLUMN from SOME_OTHER_OTHER_TABLE""";
59+
60+
enhancer = new JSqlParserQueryEnhancer(DeclaredQuery.of(s, true));
61+
62+
}
63+
}
64+
65+
@Benchmark
66+
public Object applySortWithParsing(BenchmarkParameters p) {
67+
return p.enhancer.applySorting(p.sort);
68+
}
69+
70+
}

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

-9
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,6 @@ static DeclaredQuery of(@Nullable String query, boolean nativeQuery) {
4040
return ObjectUtils.isEmpty(query) ? EmptyDeclaredQuery.EMPTY_QUERY : new StringQuery(query, nativeQuery);
4141
}
4242

43-
static boolean hasNamedParameter(String query) {
44-
45-
if (ObjectUtils.isEmpty(query)) {
46-
return false;
47-
}
48-
49-
return StringQuery.hasNamedParameter(query);
50-
}
51-
5243
/**
5344
* @return whether the underlying query has at least one named parameter.
5445
*/

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

+24-6
Original file line numberDiff line numberDiff line change
@@ -29,34 +29,52 @@
2929
public class DefaultQueryEnhancer implements QueryEnhancer {
3030

3131
private final DeclaredQuery query;
32+
private final boolean hasConstructorExpression;
33+
private final String alias;
34+
private final String projection;
35+
private final Set<String> joinAliases;
3236

3337
public DefaultQueryEnhancer(DeclaredQuery query) {
3438
this.query = query;
39+
this.hasConstructorExpression = QueryUtils.hasConstructorExpression(query.getQueryString());
40+
this.alias = QueryUtils.detectAlias(query.getQueryString());
41+
this.projection = QueryUtils.getProjection(this.query.getQueryString());
42+
this.joinAliases = QueryUtils.getOuterJoinAliases(this.query.getQueryString());
3543
}
3644

3745
@Override
38-
public String applySorting(Sort sort, @Nullable String alias) {
39-
return QueryUtils.applySorting(this.query.getQueryString(), sort, alias);
46+
public String applySorting(Sort sort) {
47+
return QueryUtils.applySorting(this.query.getQueryString(), sort, this.alias);
4048
}
4149

4250
@Override
43-
public String detectAlias() {
44-
return QueryUtils.detectAlias(this.query.getQueryString());
51+
public String applySorting(Sort sort, @Nullable String alias) {
52+
return QueryUtils.applySorting(this.query.getQueryString(), sort, alias);
4553
}
4654

4755
@Override
4856
public String createCountQueryFor(@Nullable String countProjection) {
4957
return QueryUtils.createCountQueryFor(this.query.getQueryString(), countProjection, this.query.isNativeQuery());
5058
}
5159

60+
@Override
61+
public boolean hasConstructorExpression() {
62+
return this.hasConstructorExpression;
63+
}
64+
65+
@Override
66+
public String detectAlias() {
67+
return this.alias;
68+
}
69+
5270
@Override
5371
public String getProjection() {
54-
return QueryUtils.getProjection(this.query.getQueryString());
72+
return this.projection;
5573
}
5674

5775
@Override
5876
public Set<String> getJoinAliases() {
59-
return QueryUtils.getOuterJoinAliases(this.query.getQueryString());
77+
return this.joinAliases;
6078
}
6179

6280
@Override

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

-44
This file was deleted.

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

-44
This file was deleted.

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

+18-10
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,10 @@ public QueryRendererBuilder visitFromQuery(HqlParser.FromQueryContext ctx) {
294294
@Override
295295
public QueryRendererBuilder visitQueryOrder(HqlParser.QueryOrderContext ctx) {
296296

297+
if (ctx.limitClause() == null && ctx.offsetClause() == null && ctx.fetchClause() == null) {
298+
return visit(ctx.orderByClause());
299+
}
300+
297301
QueryRendererBuilder builder = QueryRenderer.builder();
298302

299303
builder.appendExpression(visit(ctx.orderByClause()));
@@ -406,14 +410,16 @@ public QueryRendererBuilder visitJoin(HqlParser.JoinContext ctx) {
406410
@Override
407411
public QueryRendererBuilder visitJoinPath(HqlParser.JoinPathContext ctx) {
408412

409-
QueryRendererBuilder builder = QueryRenderer.builder();
410-
411-
builder.appendExpression(visit(ctx.path()));
413+
HqlParser.VariableContext variable = ctx.variable();
412414

413-
if (ctx.variable() != null) {
414-
builder.appendExpression(visit(ctx.variable()));
415+
if (variable == null) {
416+
return visit(ctx.path());
415417
}
416418

419+
QueryRendererBuilder builder = QueryRenderer.builder();
420+
builder.appendExpression(visit(ctx.path()));
421+
builder.appendExpression(visit(variable));
422+
417423
return builder;
418424
}
419425

@@ -461,14 +467,16 @@ public QueryRendererBuilder visitUpdateStatement(HqlParser.UpdateStatementContex
461467
@Override
462468
public QueryRendererBuilder visitTargetEntity(HqlParser.TargetEntityContext ctx) {
463469

464-
QueryRendererBuilder builder = QueryRenderer.builder();
470+
HqlParser.VariableContext variable = ctx.variable();
465471

466-
builder.appendExpression(visit(ctx.entityName()));
467-
468-
if (ctx.variable() != null) {
469-
builder.appendExpression(visit(ctx.variable()));
472+
if (variable == null) {
473+
return visit(ctx.entityName());
470474
}
471475

476+
QueryRendererBuilder builder = QueryRenderer.builder();
477+
builder.appendExpression(visit(ctx.entityName()));
478+
builder.appendExpression(visit(variable));
479+
472480
return builder;
473481
}
474482

0 commit comments

Comments
 (0)