diff --git a/pom.xml b/pom.xml index cbbdd63bc..bfb096551 100644 --- a/pom.xml +++ b/pom.xml @@ -47,6 +47,9 @@ 4.2.0 1.0.1 + + 1.37 + 0.4.0.BUILD-SNAPSHOT 2017 @@ -154,6 +157,98 @@ + + + jmh + + + com.github.mp911de.microbenchmark-runner + microbenchmark-runner-junit5 + ${mbr.version} + test + + + org.openjdk.jmh + jmh-core + ${jmh.version} + test + + + org.openjdk.jmh + jmh-generator-annprocess + ${jmh.version} + test + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.3.0 + + + add-source + generate-sources + + add-test-source + + + + src/jmh/java + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + true + + + + org.codehaus.mojo + exec-maven-plugin + 3.1.0 + + + run-benchmarks + pre-integration-test + + exec + + + test + java + + -classpath + + org.openjdk.jmh.Main + .* + + + + + + + + + + jitpack.io + https://jitpack.io + + + diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/AggregateReader.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/AggregateReader.java index b51b45735..c765c2eb6 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/AggregateReader.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/AggregateReader.java @@ -69,10 +69,7 @@ class AggregateReader { 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 { }; } - /** - * 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(); - } - - } } diff --git a/spring-data-relational/src/jmh/java/org/springframework/data/relational/core/sqlgeneration/BenchmarkSettings.java b/spring-data-relational/src/jmh/java/org/springframework/data/relational/core/sqlgeneration/BenchmarkSettings.java new file mode 100644 index 000000000..439824bf3 --- /dev/null +++ b/spring-data-relational/src/jmh/java/org/springframework/data/relational/core/sqlgeneration/BenchmarkSettings.java @@ -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 { + +} diff --git a/spring-data-relational/src/jmh/java/org/springframework/data/relational/core/sqlgeneration/SingleQuerySqlGeneratorBenchmark.java b/spring-data-relational/src/jmh/java/org/springframework/data/relational/core/sqlgeneration/SingleQuerySqlGeneratorBenchmark.java new file mode 100644 index 000000000..f8a1399b5 --- /dev/null +++ b/spring-data-relational/src/jmh/java/org/springframework/data/relational/core/sqlgeneration/SingleQuerySqlGeneratorBenchmark.java @@ -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 trivials) { + } + +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/DefaultAggregatePath.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/DefaultAggregatePath.java index f924ba9d3..015857e98 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/DefaultAggregatePath.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/DefaultAggregatePath.java @@ -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 { private final @Nullable PersistentPropertyPath path; + private final Lazy tableInfo = Lazy.of(() -> TableInfo.of(this)); + + private final Lazy columnInfo = Lazy.of(() -> ColumnInfo.of(this)); + @SuppressWarnings("unchecked") DefaultAggregatePath(RelationalMappingContext context, PersistentPropertyPath path) { @@ -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 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 { 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 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 { diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSelect.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSelect.java index 204c74118..e23952148 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSelect.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSelect.java @@ -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 { Assert.notNull(visitor, "Visitor must not be null"); + Consumer 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); } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSelectBuilder.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSelectBuilder.java index e06da6132..08ca542af 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSelectBuilder.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSelectBuilder.java @@ -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); - SelectValidator.validate(select); + + if (validate) { + SelectValidator.validate(select); + } return select; } @@ -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); } } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SelectBuilder.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SelectBuilder.java index 140eb7ad1..8f6c8c207 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SelectBuilder.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SelectBuilder.java @@ -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 { * @param limit rows to read. * @return {@code this} builder. */ + @Override SelectFromAndJoin limit(long limit); /** @@ -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 { * @param offset row offset, zero-based. * @return {@code this} builder. */ + @Override SelectFromAndJoin limitOffset(long limit, long offset); /** @@ -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 { * @param offset start offset. * @return {@code this} builder. */ + @Override SelectFromAndJoin offset(long offset); } @@ -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 { * 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); } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/TypedSubtreeVisitor.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/TypedSubtreeVisitor.java index 26d1e9d17..aae9091c1 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/TypedSubtreeVisitor.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/TypedSubtreeVisitor.java @@ -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 @@ -37,28 +38,35 @@ import org.springframework.lang.Nullable; * {@link Visitable}. * *

- * + * * @author Mark Paluch * @since 1.1 * @see FilteredSubtreeVisitor */ abstract class TypedSubtreeVisitor extends DelegatingVisitor { + private static final ConcurrentReferenceHashMap, 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 type) { - this.type = ResolvableType.forType(type); + TypedSubtreeVisitor(Class type) { + this.type = refCache.computeIfAbsent(type, key -> ResolvableType.forClass(type)); } /** @@ -117,7 +125,7 @@ abstract class TypedSubtreeVisitor 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 extends DelegatingVisito return leaveNested(segment); } } + } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sqlgeneration/SingleQuerySqlGenerator.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sqlgeneration/SingleQuerySqlGenerator.java index ff0a61f77..9326a55f1 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sqlgeneration/SingleQuerySqlGenerator.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sqlgeneration/SingleQuerySqlGenerator.java @@ -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 { 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 { 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));