Skip to content

Commit

Permalink
Refactor native query handling and JsqlParser usage.
Browse files Browse the repository at this point in the history
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: spring-projects#3309
Closes: spring-projects#3311
  • Loading branch information
mp911de authored and christophstrobl committed Jun 25, 2024
1 parent 3e1c8a2 commit 2dc94dd
Show file tree
Hide file tree
Showing 29 changed files with 685 additions and 770 deletions.
7 changes: 7 additions & 0 deletions spring-data-jpa-performance/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,13 @@
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
<version>${jsqlparser}</version>
<scope>test</scope>
</dependency>

</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,16 @@
*/
public interface PersonRepository extends ListCrudRepository<Person, Integer> {

List<Person> findAllByFirstname(String firstname);
List<Person> findAllByFirstname(String firstname);

List<IPersonProjection> findAllAndProjectToInterfaceByFirstname(String firstname);
List<IPersonProjection> findAllAndProjectToInterfaceByFirstname(String firstname);

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

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

@Query(value = "SELECT * FROM person WHERE firstname = ?1", nativeQuery = true)
List<Person> findAllWithNativeQueryByFirstname(String firstname);
@Query(value = "SELECT * FROM person WHERE firstname = ?1", nativeQuery = true)
List<Person> findAllWithNativeQueryByFirstname(String firstname);
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,37 @@
*/
package org.springframework.data.jpa.repository;

import java.lang.reflect.Method;

import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.provider.PersistenceProvider;
import org.springframework.data.jpa.repository.query.JpaQueryMethod;
import org.springframework.data.jpa.repository.query.StringQuery;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;

/**
* @author Mark Paluch
*/
public class Profiler {

public static void main(String[] args) throws InterruptedException {
public static void main(String[] args) throws Exception {

RepositoryFinderTests tests = new RepositoryFinderTests();
RepositoryFinderTests.BenchmarkParameters params = new RepositoryFinderTests.BenchmarkParameters();
params.doSetup();
DefaultRepositoryMetadata art = new DefaultRepositoryMetadata(PersonRepository.class);
Method method = PersonRepository.class.getMethod("findAllWithAnnotatedQueryByFirstname", String.class, Sort.class);
ProjectionFactory projectionFactory = new SpelAwareProxyProjectionFactory();

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

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


while (true) {
params.repositoryProxy.findAllWithAnnotatedQueryByFirstname("first", Sort.by("firstname"));

JpaQueryMethod queryMethod = new JpaQueryMethod(method, art, projectionFactory, PersistenceProvider.HIBERNATE);
StringQuery stringQuery = new StringQuery(queryMethod.getRequiredAnnotatedQuery(), false);
}

}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright 2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jpa.repository.query;

import jmh.mbr.junit5.Microbenchmark;

import java.io.IOException;

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Timeout;
import org.openjdk.jmh.annotations.Warmup;

import org.springframework.data.domain.Sort;

/**
* @author Mark Paluch
*/
@Microbenchmark
@Fork(1)
@Warmup(time = 2, iterations = 3)
@Measurement(time = 2)
@Timeout(time = 2)
public class JSqlParserQueryEnhancerTests {

@State(Scope.Benchmark)
public static class BenchmarkParameters {

JSqlParserQueryEnhancer enhancer;
Sort sort = Sort.by("foo");
private byte[] serialized;

@Setup(Level.Iteration)
public void doSetup() throws IOException {

String s = """
select SOME_COLUMN from SOME_TABLE where REPORTING_DATE = :REPORTING_DATE
except
select SOME_COLUMN from SOME_OTHER_TABLE where REPORTING_DATE = :REPORTING_DATE
union select SOME_COLUMN from SOME_OTHER_OTHER_TABLE""";

enhancer = new JSqlParserQueryEnhancer(DeclaredQuery.of(s, true));

}
}

@Benchmark
public Object applySortWithParsing(BenchmarkParameters p) {
return p.enhancer.applySorting(p.sort);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,6 @@ static DeclaredQuery of(@Nullable String query, boolean nativeQuery) {
return ObjectUtils.isEmpty(query) ? EmptyDeclaredQuery.EMPTY_QUERY : new StringQuery(query, nativeQuery);
}

static boolean hasNamedParameter(String query) {

if (ObjectUtils.isEmpty(query)) {
return false;
}

return StringQuery.hasNamedParameter(query);
}

/**
* @return whether the underlying query has at least one named parameter.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,34 +29,52 @@
public class DefaultQueryEnhancer implements QueryEnhancer {

private final DeclaredQuery query;
private final boolean hasConstructorExpression;
private final String alias;
private final String projection;
private final Set<String> joinAliases;

public DefaultQueryEnhancer(DeclaredQuery query) {
this.query = query;
this.hasConstructorExpression = QueryUtils.hasConstructorExpression(query.getQueryString());
this.alias = QueryUtils.detectAlias(query.getQueryString());
this.projection = QueryUtils.getProjection(this.query.getQueryString());
this.joinAliases = QueryUtils.getOuterJoinAliases(this.query.getQueryString());
}

@Override
public String applySorting(Sort sort, @Nullable String alias) {
return QueryUtils.applySorting(this.query.getQueryString(), sort, alias);
public String applySorting(Sort sort) {
return QueryUtils.applySorting(this.query.getQueryString(), sort, this.alias);
}

@Override
public String detectAlias() {
return QueryUtils.detectAlias(this.query.getQueryString());
public String applySorting(Sort sort, @Nullable String alias) {
return QueryUtils.applySorting(this.query.getQueryString(), sort, alias);
}

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

@Override
public boolean hasConstructorExpression() {
return this.hasConstructorExpression;
}

@Override
public String detectAlias() {
return this.alias;
}

@Override
public String getProjection() {
return QueryUtils.getProjection(this.query.getQueryString());
return this.projection;
}

@Override
public Set<String> getJoinAliases() {
return QueryUtils.getOuterJoinAliases(this.query.getQueryString());
return this.joinAliases;
}

@Override
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,10 @@ public QueryRendererBuilder visitFromQuery(HqlParser.FromQueryContext ctx) {
@Override
public QueryRendererBuilder visitQueryOrder(HqlParser.QueryOrderContext ctx) {

if (ctx.limitClause() == null && ctx.offsetClause() == null && ctx.fetchClause() == null) {
return visit(ctx.orderByClause());
}

QueryRendererBuilder builder = QueryRenderer.builder();

builder.appendExpression(visit(ctx.orderByClause()));
Expand Down Expand Up @@ -406,14 +410,16 @@ public QueryRendererBuilder visitJoin(HqlParser.JoinContext ctx) {
@Override
public QueryRendererBuilder visitJoinPath(HqlParser.JoinPathContext ctx) {

QueryRendererBuilder builder = QueryRenderer.builder();

builder.appendExpression(visit(ctx.path()));
HqlParser.VariableContext variable = ctx.variable();

if (ctx.variable() != null) {
builder.appendExpression(visit(ctx.variable()));
if (variable == null) {
return visit(ctx.path());
}

QueryRendererBuilder builder = QueryRenderer.builder();
builder.appendExpression(visit(ctx.path()));
builder.appendExpression(visit(variable));

return builder;
}

Expand Down Expand Up @@ -461,14 +467,16 @@ public QueryRendererBuilder visitUpdateStatement(HqlParser.UpdateStatementContex
@Override
public QueryRendererBuilder visitTargetEntity(HqlParser.TargetEntityContext ctx) {

QueryRendererBuilder builder = QueryRenderer.builder();
HqlParser.VariableContext variable = ctx.variable();

builder.appendExpression(visit(ctx.entityName()));

if (ctx.variable() != null) {
builder.appendExpression(visit(ctx.variable()));
if (variable == null) {
return visit(ctx.entityName());
}

QueryRendererBuilder builder = QueryRenderer.builder();
builder.appendExpression(visit(ctx.entityName()));
builder.appendExpression(visit(variable));

return builder;
}

Expand Down
Loading

0 comments on commit 2dc94dd

Please sign in to comment.