Browse Source

Add JMH benchmarks and optimizations to affected components.

See #1601
Original pull request: #1617
pull/1622/head
Mark Paluch 2 years ago
parent
commit
7b27d0e08c
No known key found for this signature in database
GPG Key ID: 4406B84C1661DCD1
  1. 95
      pom.xml
  2. 39
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/AggregateReader.java
  3. 40
      spring-data-relational/src/jmh/java/org/springframework/data/relational/core/sqlgeneration/BenchmarkSettings.java
  4. 66
      spring-data-relational/src/jmh/java/org/springframework/data/relational/core/sqlgeneration/SingleQuerySqlGeneratorBenchmark.java
  5. 34
      spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/DefaultAggregatePath.java
  6. 7
      spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSelect.java
  7. 9
      spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSelectBuilder.java
  8. 26
      spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SelectBuilder.java
  9. 17
      spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/TypedSubtreeVisitor.java
  10. 6
      spring-data-relational/src/main/java/org/springframework/data/relational/core/sqlgeneration/SingleQuerySqlGenerator.java

95
pom.xml

@ -47,6 +47,9 @@ @@ -47,6 +47,9 @@
<!-- test utilities-->
<awaitility.version>4.2.0</awaitility.version>
<archunit.version>1.0.1</archunit.version>
<jmh.version>1.37</jmh.version>
<mbr.version>0.4.0.BUILD-SNAPSHOT</mbr.version>
</properties>
<inceptionYear>2017</inceptionYear>
@ -154,6 +157,98 @@ @@ -154,6 +157,98 @@
</plugins>
</build>
</profile>
<profile>
<id>jmh</id>
<dependencies>
<dependency>
<groupId>com.github.mp911de.microbenchmark-runner</groupId>
<artifactId>microbenchmark-runner-junit5</artifactId>
<version>${mbr.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>${jmh.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>${jmh.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.3.0</version>
<executions>
<execution>
<id>add-source</id>
<phase>generate-sources</phase>
<goals>
<goal>add-test-source</goal>
</goals>
<configuration>
<sources>
<source>src/jmh/java</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>run-benchmarks</id>
<phase>pre-integration-test</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<classpathScope>test</classpathScope>
<executable>java</executable>
<arguments>
<argument>-classpath</argument>
<classpath/>
<argument>org.openjdk.jmh.Main</argument>
<argument>.*</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
</profile>
</profiles>
<build>

39
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/AggregateReader.java

@ -69,10 +69,7 @@ class AggregateReader<T> { @@ -69,10 +69,7 @@ class AggregateReader<T> {
this.aggregate = aggregate;
this.jdbcTemplate = jdbcTemplate;
this.table = Table.create(aggregate.getQualifiedTableName());
this.sqlGenerator = new CachingSqlGenerator(
new SingleQuerySqlGenerator(converter.getMappingContext(), aliasFactory, dialect, aggregate));
this.sqlGenerator = new SingleQuerySqlGenerator(converter.getMappingContext(), aliasFactory, dialect, aggregate);
this.extractor = new RowDocumentResultSetExtractor(converter.getMappingContext(),
createPathToColumnMapping(aliasFactory));
}
@ -187,38 +184,4 @@ class AggregateReader<T> { @@ -187,38 +184,4 @@ class AggregateReader<T> {
};
}
/**
* A wrapper for the {@link org.springframework.data.relational.core.sqlgeneration.SqlGenerator} that caches the
* generated statements.
*
* @author Jens Schauder
* @since 3.2
*/
static class CachingSqlGenerator implements SqlGenerator {
private final SqlGenerator delegate;
private final String findAll;
public CachingSqlGenerator(SqlGenerator delegate) {
this.delegate = delegate;
this.findAll = delegate.findAll();
}
@Override
public String findAll() {
return findAll;
}
@Override
public String findAll(@Nullable Condition condition) {
return delegate.findAll(condition);
}
@Override
public AliasFactory getAliasFactory() {
return delegate.getAliasFactory();
}
}
}

40
spring-data-relational/src/jmh/java/org/springframework/data/relational/core/sqlgeneration/BenchmarkSettings.java

@ -0,0 +1,40 @@ @@ -0,0 +1,40 @@
/*
* Copyright 2019-2022 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.relational.core.sqlgeneration;
import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Warmup;
/**
* Global benchmark settings.
*
* @author Mark Paluch
*/
@Warmup(iterations = 5, time = 2000, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 5, time = 1000, timeUnit = TimeUnit.MILLISECONDS)
@Fork(value = 1, warmups = 0)
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
public abstract class BenchmarkSettings {
}

66
spring-data-relational/src/jmh/java/org/springframework/data/relational/core/sqlgeneration/SingleQuerySqlGeneratorBenchmark.java

@ -0,0 +1,66 @@ @@ -0,0 +1,66 @@
/*
* Copyright 2023 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.relational.core.sqlgeneration;
import jmh.mbr.junit5.Microbenchmark;
import java.util.List;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.dialect.PostgresDialect;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
/**
* Benchmark for {@link SingleQuerySqlGenerator}.
*
* @author Mark Paluch
*/
@Microbenchmark
public class SingleQuerySqlGeneratorBenchmark extends BenchmarkSettings {
@Benchmark
public String findAll(StateHolder state) {
return new SingleQuerySqlGenerator(state.context, state.aliasFactory, PostgresDialect.INSTANCE,
state.persistentEntity).findAll(null);
}
@State(Scope.Benchmark)
public static class StateHolder {
RelationalMappingContext context = new RelationalMappingContext();
RelationalPersistentEntity<?> persistentEntity;
AliasFactory aliasFactory = new AliasFactory();
@Setup
public void setup() {
persistentEntity = context.getRequiredPersistentEntity(SingleReferenceAggregate.class);
}
}
record TrivialAggregate(@Id Long id, String name) {
}
record SingleReferenceAggregate(@Id Long id, String name, List<TrivialAggregate> trivials) {
}
}

34
spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/DefaultAggregatePath.java

@ -20,6 +20,7 @@ import java.util.NoSuchElementException; @@ -20,6 +20,7 @@ import java.util.NoSuchElementException;
import java.util.Objects;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.util.Lazy;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@ -38,6 +39,10 @@ class DefaultAggregatePath implements AggregatePath { @@ -38,6 +39,10 @@ class DefaultAggregatePath implements AggregatePath {
private final @Nullable PersistentPropertyPath<RelationalPersistentProperty> path;
private final Lazy<TableInfo> tableInfo = Lazy.of(() -> TableInfo.of(this));
private final Lazy<ColumnInfo> columnInfo = Lazy.of(() -> ColumnInfo.of(this));
@SuppressWarnings("unchecked")
DefaultAggregatePath(RelationalMappingContext context,
PersistentPropertyPath<? extends RelationalPersistentProperty> path) {
@ -189,14 +194,24 @@ class DefaultAggregatePath implements AggregatePath { @@ -189,14 +194,24 @@ class DefaultAggregatePath implements AggregatePath {
return AggregatePathTraversal.getTableOwningPath(this);
}
/**
* Creates an {@link Iterator} that iterates over the current path and all ancestors. It will start with the current
* path, followed by its parent until ending with the root.
*/
@Override
public String toString() {
return "AggregatePath["
+ (rootType == null ? path.getBaseProperty().getOwner().getType().getName() : rootType.getName()) + "]"
+ ((isRoot()) ? "/" : path.toDotPath());
public Iterator<AggregatePath> iterator() {
return new AggregatePathIterator(this);
}
@Override
public TableInfo getTableInfo() {
return this.tableInfo.get();
}
@Override
public ColumnInfo getColumnInfo() {
return this.columnInfo.get();
}
@Override
public boolean equals(Object o) {
@ -215,13 +230,12 @@ class DefaultAggregatePath implements AggregatePath { @@ -215,13 +230,12 @@ class DefaultAggregatePath implements AggregatePath {
return Objects.hash(context, rootType, path);
}
/**
* Creates an {@link Iterator} that iterates over the current path and all ancestors. It will start with the current
* path, followed by its parent until ending with the root.
*/
@Override
public Iterator<AggregatePath> iterator() {
return new AggregatePathIterator(this);
public String toString() {
return "AggregatePath["
+ (rootType == null ? path.getBaseProperty().getOwner().getType().getName() : rootType.getName()) + "]"
+ ((isRoot()) ? "/" : path.toDotPath());
}
private static class AggregatePathIterator implements Iterator<AggregatePath> {

7
spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSelect.java

@ -19,6 +19,7 @@ import java.util.ArrayList; @@ -19,6 +19,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.OptionalLong;
import java.util.function.Consumer;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@ -92,15 +93,17 @@ class DefaultSelect implements Select { @@ -92,15 +93,17 @@ class DefaultSelect implements Select {
Assert.notNull(visitor, "Visitor must not be null");
Consumer<? super AbstractSegment> action = it -> it.visit(visitor);
visitor.enter(this);
selectList.visit(visitor);
from.visit(visitor);
joins.forEach(it -> it.visit(visitor));
joins.forEach(action);
visitIfNotNull(where, visitor);
orderBy.forEach(it -> it.visit(visitor));
orderBy.forEach(action);
visitor.leave(this);
}

9
spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSelectBuilder.java

@ -200,11 +200,14 @@ class DefaultSelectBuilder implements SelectBuilder, SelectAndFrom, SelectFromAn @@ -200,11 +200,14 @@ class DefaultSelectBuilder implements SelectBuilder, SelectAndFrom, SelectFromAn
}
@Override
public Select build() {
public Select build(boolean validate) {
DefaultSelect select = new DefaultSelect(distinct, selectList, from, limit, offset, joins, where, orderBy,
lockMode);
if (validate) {
SelectValidator.validate(select);
}
return select;
}
@ -359,9 +362,9 @@ class DefaultSelectBuilder implements SelectBuilder, SelectAndFrom, SelectFromAn @@ -359,9 +362,9 @@ class DefaultSelectBuilder implements SelectBuilder, SelectAndFrom, SelectFromAn
}
@Override
public Select build() {
public Select build(boolean validate) {
selectBuilder.join(finishJoin());
return selectBuilder.build();
return selectBuilder.build(validate);
}
}
}

26
spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SelectBuilder.java

@ -297,6 +297,7 @@ public interface SelectBuilder { @@ -297,6 +297,7 @@ public interface SelectBuilder {
* @param offset row offset, zero-based.
* @return {@code this} builder.
*/
@Override
SelectFromAndJoin limitOffset(long limit, long offset);
/**
@ -305,6 +306,7 @@ public interface SelectBuilder { @@ -305,6 +306,7 @@ public interface SelectBuilder {
* @param limit rows to read.
* @return {@code this} builder.
*/
@Override
SelectFromAndJoin limit(long limit);
/**
@ -313,6 +315,7 @@ public interface SelectBuilder { @@ -313,6 +315,7 @@ public interface SelectBuilder {
* @param offset start offset.
* @return {@code this} builder.
*/
@Override
SelectFromAndJoin offset(long offset);
}
@ -331,6 +334,7 @@ public interface SelectBuilder { @@ -331,6 +334,7 @@ public interface SelectBuilder {
* @param offset row offset, zero-based.
* @return {@code this} builder.
*/
@Override
SelectFromAndJoin limitOffset(long limit, long offset);
/**
@ -339,6 +343,7 @@ public interface SelectBuilder { @@ -339,6 +343,7 @@ public interface SelectBuilder {
* @param limit rows to read.
* @return {@code this} builder.
*/
@Override
SelectFromAndJoin limit(long limit);
/**
@ -347,6 +352,7 @@ public interface SelectBuilder { @@ -347,6 +352,7 @@ public interface SelectBuilder {
* @param offset start offset.
* @return {@code this} builder.
*/
@Override
SelectFromAndJoin offset(long offset);
}
@ -488,11 +494,11 @@ public interface SelectBuilder { @@ -488,11 +494,11 @@ public interface SelectBuilder {
SelectOn leftOuterJoin(TableLike table);
/**
* Declar a join, where the join type ({@code INNER}, {@code LEFT OUTER}, {@code RIGHT OUTER}, {@code FULL OUTER})
* Declare a join, where the join type ({@code INNER}, {@code LEFT OUTER}, {@code RIGHT OUTER}, {@code FULL OUTER})
* is specified by an extra argument.
*
* @param table the table to join. Must not be {@literal null}.
* @param joinType the type of joi. Must not be {@literal null}.
* @param joinType the type of join. Must not be {@literal null}.
* @return {@code this} builder.
*/
SelectOn join(TableLike table, Join.JoinType joinType);
@ -577,8 +583,20 @@ public interface SelectBuilder { @@ -577,8 +583,20 @@ public interface SelectBuilder {
* Build the {@link Select} statement and verify basic relationship constraints such as all referenced columns have
* a {@code FROM} or {@code JOIN} table import.
*
* @return the build and immutable {@link Select} statement.
* @return the built and immutable {@link Select} statement.
*/
default Select build() {
return build(true);
}
/**
* Build the {@link Select} statement.
*
* @param validate whether to validate the generated select by checking basic relationship constraints such as all
* referenced columns have a {@code FROM} or {@code JOIN} table import.
* @return the built and immutable {@link Select} statement.
* @since 3.2
*/
Select build();
Select build(boolean validate);
}
}

17
spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/TypedSubtreeVisitor.java

@ -21,6 +21,7 @@ import org.springframework.core.ResolvableType; @@ -21,6 +21,7 @@ import org.springframework.core.ResolvableType;
import org.springframework.data.relational.core.sql.Visitable;
import org.springframework.data.relational.core.sql.Visitor;
import org.springframework.lang.Nullable;
import org.springframework.util.ConcurrentReferenceHashMap;
/**
* Type-filtering {@link DelegatingVisitor visitor} applying a {@link Class type filter} derived from the generic type
@ -44,21 +45,28 @@ import org.springframework.lang.Nullable; @@ -44,21 +45,28 @@ import org.springframework.lang.Nullable;
*/
abstract class TypedSubtreeVisitor<T extends Visitable> extends DelegatingVisitor {
private static final ConcurrentReferenceHashMap<Class<?>, ResolvableType> refCache = new ConcurrentReferenceHashMap<>();
private final ResolvableType type;
private @Nullable Visitable currentSegment;
enum Assignable {
YES, NO,
}
/**
* Creates a new {@link TypedSubtreeVisitor}.
*/
TypedSubtreeVisitor() {
this.type = ResolvableType.forClass(getClass()).as(TypedSubtreeVisitor.class).getGeneric(0);
this.type = refCache.computeIfAbsent(this.getClass(),
key -> ResolvableType.forClass(key).as(TypedSubtreeVisitor.class).getGeneric(0));
}
/**
* Creates a new {@link TypedSubtreeVisitor} with an explicitly provided type.
*/
TypedSubtreeVisitor(Class <T> type) {
this.type = ResolvableType.forType(type);
TypedSubtreeVisitor(Class<T> type) {
this.type = refCache.computeIfAbsent(type, key -> ResolvableType.forClass(type));
}
/**
@ -117,7 +125,7 @@ abstract class TypedSubtreeVisitor<T extends Visitable> extends DelegatingVisito @@ -117,7 +125,7 @@ abstract class TypedSubtreeVisitor<T extends Visitable> extends DelegatingVisito
if (currentSegment == null) {
if (this.type.isInstance(segment)) {
if (type.isInstance(segment)) {
currentSegment = segment;
return enterMatched((T) segment);
@ -142,4 +150,5 @@ abstract class TypedSubtreeVisitor<T extends Visitable> extends DelegatingVisito @@ -142,4 +150,5 @@ abstract class TypedSubtreeVisitor<T extends Visitable> extends DelegatingVisito
return leaveNested(segment);
}
}
}

6
spring-data-relational/src/main/java/org/springframework/data/relational/core/sqlgeneration/SingleQuerySqlGenerator.java

@ -104,7 +104,7 @@ public class SingleQuerySqlGenerator implements SqlGenerator { @@ -104,7 +104,7 @@ public class SingleQuerySqlGenerator implements SqlGenerator {
finalColumns.add(rootIdExpression);
Select fullQuery = StatementBuilder.select(finalColumns).from(inlineQuery).orderBy(rootIdExpression, just("rn"))
.build();
.build(false);
return SqlRenderer.create(new RenderContextFactory(dialect).createRenderContext()).render(fullQuery);
}
@ -118,7 +118,7 @@ public class SingleQuerySqlGenerator implements SqlGenerator { @@ -118,7 +118,7 @@ public class SingleQuerySqlGenerator implements SqlGenerator {
select = applyJoins(rootPath, inlineQueries, select);
SelectBuilder.BuildSelect buildSelect = applyWhereCondition(rootPath, inlineQueries, select);
Select mainSelect = buildSelect.build();
Select mainSelect = buildSelect.build(false);
return InlineQuery.create(mainSelect, "main");
}
@ -215,7 +215,7 @@ public class SingleQuerySqlGenerator implements SqlGenerator { @@ -215,7 +215,7 @@ public class SingleQuerySqlGenerator implements SqlGenerator {
SelectBuilder.BuildSelect buildSelect = condition != null ? select.where(condition) : select;
InlineQuery inlineQuery = InlineQuery.create(buildSelect.build(),
InlineQuery inlineQuery = InlineQuery.create(buildSelect.build(false),
aliases.getTableAlias(context.getAggregatePath(entity)));
return QueryMeta.of(basePath, inlineQuery, columnAliases, just(id), just(backReferenceAlias), just(keyAlias),
just(rowNumberAlias), just(rowCountAlias));

Loading…
Cancel
Save