Skip to content

Commit 7b27d0e

Browse files
committed
Add JMH benchmarks and optimizations to affected components.
See #1601 Original pull request: #1617
1 parent f3bc0af commit 7b27d0e

File tree

10 files changed

+278
-67
lines changed

10 files changed

+278
-67
lines changed

pom.xml

+95
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@
4747
<!-- test utilities-->
4848
<awaitility.version>4.2.0</awaitility.version>
4949
<archunit.version>1.0.1</archunit.version>
50+
51+
<jmh.version>1.37</jmh.version>
52+
<mbr.version>0.4.0.BUILD-SNAPSHOT</mbr.version>
5053
</properties>
5154

5255
<inceptionYear>2017</inceptionYear>
@@ -154,6 +157,98 @@
154157
</plugins>
155158
</build>
156159
</profile>
160+
161+
<profile>
162+
<id>jmh</id>
163+
<dependencies>
164+
<dependency>
165+
<groupId>com.github.mp911de.microbenchmark-runner</groupId>
166+
<artifactId>microbenchmark-runner-junit5</artifactId>
167+
<version>${mbr.version}</version>
168+
<scope>test</scope>
169+
</dependency>
170+
<dependency>
171+
<groupId>org.openjdk.jmh</groupId>
172+
<artifactId>jmh-core</artifactId>
173+
<version>${jmh.version}</version>
174+
<scope>test</scope>
175+
</dependency>
176+
<dependency>
177+
<groupId>org.openjdk.jmh</groupId>
178+
<artifactId>jmh-generator-annprocess</artifactId>
179+
<version>${jmh.version}</version>
180+
<scope>test</scope>
181+
</dependency>
182+
</dependencies>
183+
<build>
184+
<plugins>
185+
<plugin>
186+
<groupId>org.codehaus.mojo</groupId>
187+
<artifactId>build-helper-maven-plugin</artifactId>
188+
<version>3.3.0</version>
189+
<executions>
190+
<execution>
191+
<id>add-source</id>
192+
<phase>generate-sources</phase>
193+
<goals>
194+
<goal>add-test-source</goal>
195+
</goals>
196+
<configuration>
197+
<sources>
198+
<source>src/jmh/java</source>
199+
</sources>
200+
</configuration>
201+
</execution>
202+
</executions>
203+
</plugin>
204+
<plugin>
205+
<groupId>org.apache.maven.plugins</groupId>
206+
<artifactId>maven-surefire-plugin</artifactId>
207+
<configuration>
208+
<skip>true</skip>
209+
</configuration>
210+
</plugin>
211+
212+
<plugin>
213+
<groupId>org.apache.maven.plugins</groupId>
214+
<artifactId>maven-failsafe-plugin</artifactId>
215+
<configuration>
216+
<skip>true</skip>
217+
</configuration>
218+
</plugin>
219+
<plugin>
220+
<groupId>org.codehaus.mojo</groupId>
221+
<artifactId>exec-maven-plugin</artifactId>
222+
<version>3.1.0</version>
223+
<executions>
224+
<execution>
225+
<id>run-benchmarks</id>
226+
<phase>pre-integration-test</phase>
227+
<goals>
228+
<goal>exec</goal>
229+
</goals>
230+
<configuration>
231+
<classpathScope>test</classpathScope>
232+
<executable>java</executable>
233+
<arguments>
234+
<argument>-classpath</argument>
235+
<classpath/>
236+
<argument>org.openjdk.jmh.Main</argument>
237+
<argument>.*</argument>
238+
</arguments>
239+
</configuration>
240+
</execution>
241+
</executions>
242+
</plugin>
243+
</plugins>
244+
</build>
245+
<repositories>
246+
<repository>
247+
<id>jitpack.io</id>
248+
<url>https://jitpack.io</url>
249+
</repository>
250+
</repositories>
251+
</profile>
157252
</profiles>
158253

159254
<build>

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

+1-38
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,7 @@ class AggregateReader<T> {
6969
this.aggregate = aggregate;
7070
this.jdbcTemplate = jdbcTemplate;
7171
this.table = Table.create(aggregate.getQualifiedTableName());
72-
73-
this.sqlGenerator = new CachingSqlGenerator(
74-
new SingleQuerySqlGenerator(converter.getMappingContext(), aliasFactory, dialect, aggregate));
75-
72+
this.sqlGenerator = new SingleQuerySqlGenerator(converter.getMappingContext(), aliasFactory, dialect, aggregate);
7673
this.extractor = new RowDocumentResultSetExtractor(converter.getMappingContext(),
7774
createPathToColumnMapping(aliasFactory));
7875
}
@@ -187,38 +184,4 @@ public String keyColumn(AggregatePath path) {
187184
};
188185
}
189186

190-
/**
191-
* A wrapper for the {@link org.springframework.data.relational.core.sqlgeneration.SqlGenerator} that caches the
192-
* generated statements.
193-
*
194-
* @author Jens Schauder
195-
* @since 3.2
196-
*/
197-
static class CachingSqlGenerator implements SqlGenerator {
198-
199-
private final SqlGenerator delegate;
200-
private final String findAll;
201-
202-
public CachingSqlGenerator(SqlGenerator delegate) {
203-
204-
this.delegate = delegate;
205-
this.findAll = delegate.findAll();
206-
}
207-
208-
@Override
209-
public String findAll() {
210-
return findAll;
211-
}
212-
213-
@Override
214-
public String findAll(@Nullable Condition condition) {
215-
return delegate.findAll(condition);
216-
}
217-
218-
@Override
219-
public AliasFactory getAliasFactory() {
220-
return delegate.getAliasFactory();
221-
}
222-
223-
}
224187
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2019-2022 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+
17+
package org.springframework.data.relational.core.sqlgeneration;
18+
19+
import java.util.concurrent.TimeUnit;
20+
21+
import org.openjdk.jmh.annotations.BenchmarkMode;
22+
import org.openjdk.jmh.annotations.Fork;
23+
import org.openjdk.jmh.annotations.Measurement;
24+
import org.openjdk.jmh.annotations.Mode;
25+
import org.openjdk.jmh.annotations.OutputTimeUnit;
26+
import org.openjdk.jmh.annotations.Warmup;
27+
28+
/**
29+
* Global benchmark settings.
30+
*
31+
* @author Mark Paluch
32+
*/
33+
@Warmup(iterations = 5, time = 2000, timeUnit = TimeUnit.MILLISECONDS)
34+
@Measurement(iterations = 5, time = 1000, timeUnit = TimeUnit.MILLISECONDS)
35+
@Fork(value = 1, warmups = 0)
36+
@BenchmarkMode(Mode.Throughput)
37+
@OutputTimeUnit(TimeUnit.SECONDS)
38+
public abstract class BenchmarkSettings {
39+
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright 2023 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.relational.core.sqlgeneration;
17+
18+
import jmh.mbr.junit5.Microbenchmark;
19+
20+
import java.util.List;
21+
22+
import org.openjdk.jmh.annotations.Benchmark;
23+
import org.openjdk.jmh.annotations.Scope;
24+
import org.openjdk.jmh.annotations.Setup;
25+
import org.openjdk.jmh.annotations.State;
26+
import org.springframework.data.annotation.Id;
27+
import org.springframework.data.relational.core.dialect.PostgresDialect;
28+
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
29+
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
30+
31+
/**
32+
* Benchmark for {@link SingleQuerySqlGenerator}.
33+
*
34+
* @author Mark Paluch
35+
*/
36+
@Microbenchmark
37+
public class SingleQuerySqlGeneratorBenchmark extends BenchmarkSettings {
38+
39+
@Benchmark
40+
public String findAll(StateHolder state) {
41+
return new SingleQuerySqlGenerator(state.context, state.aliasFactory, PostgresDialect.INSTANCE,
42+
state.persistentEntity).findAll(null);
43+
}
44+
45+
@State(Scope.Benchmark)
46+
public static class StateHolder {
47+
48+
RelationalMappingContext context = new RelationalMappingContext();
49+
50+
RelationalPersistentEntity<?> persistentEntity;
51+
52+
AliasFactory aliasFactory = new AliasFactory();
53+
54+
@Setup
55+
public void setup() {
56+
persistentEntity = context.getRequiredPersistentEntity(SingleReferenceAggregate.class);
57+
}
58+
}
59+
60+
record TrivialAggregate(@Id Long id, String name) {
61+
}
62+
63+
record SingleReferenceAggregate(@Id Long id, String name, List<TrivialAggregate> trivials) {
64+
}
65+
66+
}

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

+24-10
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.util.Objects;
2121

2222
import org.springframework.data.mapping.PersistentPropertyPath;
23+
import org.springframework.data.util.Lazy;
2324
import org.springframework.lang.Nullable;
2425
import org.springframework.util.Assert;
2526

@@ -38,6 +39,10 @@ class DefaultAggregatePath implements AggregatePath {
3839

3940
private final @Nullable PersistentPropertyPath<RelationalPersistentProperty> path;
4041

42+
private final Lazy<TableInfo> tableInfo = Lazy.of(() -> TableInfo.of(this));
43+
44+
private final Lazy<ColumnInfo> columnInfo = Lazy.of(() -> ColumnInfo.of(this));
45+
4146
@SuppressWarnings("unchecked")
4247
DefaultAggregatePath(RelationalMappingContext context,
4348
PersistentPropertyPath<? extends RelationalPersistentProperty> path) {
@@ -189,14 +194,24 @@ private AggregatePath getTableOwningAncestor() {
189194
return AggregatePathTraversal.getTableOwningPath(this);
190195
}
191196

197+
/**
198+
* Creates an {@link Iterator} that iterates over the current path and all ancestors. It will start with the current
199+
* path, followed by its parent until ending with the root.
200+
*/
192201
@Override
193-
public String toString() {
194-
return "AggregatePath["
195-
+ (rootType == null ? path.getBaseProperty().getOwner().getType().getName() : rootType.getName()) + "]"
196-
+ ((isRoot()) ? "/" : path.toDotPath());
202+
public Iterator<AggregatePath> iterator() {
203+
return new AggregatePathIterator(this);
197204
}
198205

206+
@Override
207+
public TableInfo getTableInfo() {
208+
return this.tableInfo.get();
209+
}
199210

211+
@Override
212+
public ColumnInfo getColumnInfo() {
213+
return this.columnInfo.get();
214+
}
200215

201216
@Override
202217
public boolean equals(Object o) {
@@ -215,13 +230,12 @@ public int hashCode() {
215230
return Objects.hash(context, rootType, path);
216231
}
217232

218-
/**
219-
* Creates an {@link Iterator} that iterates over the current path and all ancestors. It will start with the current
220-
* path, followed by its parent until ending with the root.
221-
*/
233+
222234
@Override
223-
public Iterator<AggregatePath> iterator() {
224-
return new AggregatePathIterator(this);
235+
public String toString() {
236+
return "AggregatePath["
237+
+ (rootType == null ? path.getBaseProperty().getOwner().getType().getName() : rootType.getName()) + "]"
238+
+ ((isRoot()) ? "/" : path.toDotPath());
225239
}
226240

227241
private static class AggregatePathIterator implements Iterator<AggregatePath> {

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

+5-2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.Collections;
2020
import java.util.List;
2121
import java.util.OptionalLong;
22+
import java.util.function.Consumer;
2223

2324
import org.springframework.lang.Nullable;
2425
import org.springframework.util.Assert;
@@ -92,15 +93,17 @@ public void visit(Visitor visitor) {
9293

9394
Assert.notNull(visitor, "Visitor must not be null");
9495

96+
Consumer<? super AbstractSegment> action = it -> it.visit(visitor);
97+
9598
visitor.enter(this);
9699

97100
selectList.visit(visitor);
98101
from.visit(visitor);
99-
joins.forEach(it -> it.visit(visitor));
102+
joins.forEach(action);
100103

101104
visitIfNotNull(where, visitor);
102105

103-
orderBy.forEach(it -> it.visit(visitor));
106+
orderBy.forEach(action);
104107

105108
visitor.leave(this);
106109
}

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

+7-4
Original file line numberDiff line numberDiff line change
@@ -200,11 +200,14 @@ public SelectLock lock(LockMode lockMode) {
200200
}
201201

202202
@Override
203-
public Select build() {
203+
public Select build(boolean validate) {
204204

205205
DefaultSelect select = new DefaultSelect(distinct, selectList, from, limit, offset, joins, where, orderBy,
206206
lockMode);
207-
SelectValidator.validate(select);
207+
208+
if (validate) {
209+
SelectValidator.validate(select);
210+
}
208211
return select;
209212
}
210213

@@ -359,9 +362,9 @@ public SelectLock lock(LockMode lockMode) {
359362
}
360363

361364
@Override
362-
public Select build() {
365+
public Select build(boolean validate) {
363366
selectBuilder.join(finishJoin());
364-
return selectBuilder.build();
367+
return selectBuilder.build(validate);
365368
}
366369
}
367370
}

0 commit comments

Comments
 (0)