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 extends RelationalPersistentProperty> 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 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);
}
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));