Skip to content

Commit 818d30c

Browse files
committed
Polishing.
Add benchmarks. Performance refinements. See #574 Original pull request #1957
1 parent 3298a5e commit 818d30c

30 files changed

+796
-421
lines changed

pom.xml

Lines changed: 3 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2-
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
35

46
<modelVersion>4.0.0</modelVersion>
57

@@ -175,68 +177,6 @@
175177
<scope>test</scope>
176178
</dependency>
177179
</dependencies>
178-
<build>
179-
<plugins>
180-
<plugin>
181-
<groupId>org.codehaus.mojo</groupId>
182-
<artifactId>build-helper-maven-plugin</artifactId>
183-
<version>3.3.0</version>
184-
<executions>
185-
<execution>
186-
<id>add-source</id>
187-
<phase>generate-sources</phase>
188-
<goals>
189-
<goal>add-test-source</goal>
190-
</goals>
191-
<configuration>
192-
<sources>
193-
<source>src/jmh/java</source>
194-
</sources>
195-
</configuration>
196-
</execution>
197-
</executions>
198-
</plugin>
199-
<plugin>
200-
<groupId>org.apache.maven.plugins</groupId>
201-
<artifactId>maven-surefire-plugin</artifactId>
202-
<configuration>
203-
<skip>true</skip>
204-
</configuration>
205-
</plugin>
206-
207-
<plugin>
208-
<groupId>org.apache.maven.plugins</groupId>
209-
<artifactId>maven-failsafe-plugin</artifactId>
210-
<configuration>
211-
<skip>true</skip>
212-
</configuration>
213-
</plugin>
214-
<plugin>
215-
<groupId>org.codehaus.mojo</groupId>
216-
<artifactId>exec-maven-plugin</artifactId>
217-
<version>3.1.0</version>
218-
<executions>
219-
<execution>
220-
<id>run-benchmarks</id>
221-
<phase>pre-integration-test</phase>
222-
<goals>
223-
<goal>exec</goal>
224-
</goals>
225-
<configuration>
226-
<classpathScope>test</classpathScope>
227-
<executable>java</executable>
228-
<arguments>
229-
<argument>-classpath</argument>
230-
<classpath/>
231-
<argument>org.openjdk.jmh.Main</argument>
232-
<argument>.*</argument>
233-
</arguments>
234-
</configuration>
235-
</execution>
236-
</executions>
237-
</plugin>
238-
</plugins>
239-
</build>
240180
<repositories>
241181
<repository>
242182
<id>jitpack.io</id>
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2023-2025 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.jdbc;
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 = 1000, 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+
}
Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
/*
2+
* Copyright 2025 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.jdbc;
17+
18+
import static org.assertj.core.api.Assertions.*;
19+
20+
import java.sql.Connection;
21+
import java.sql.SQLException;
22+
import java.util.List;
23+
import java.util.Map;
24+
import java.util.concurrent.atomic.AtomicLong;
25+
26+
import javax.sql.DataSource;
27+
28+
import org.junit.platform.commons.annotation.Testable;
29+
import org.openjdk.jmh.annotations.Benchmark;
30+
import org.openjdk.jmh.annotations.Scope;
31+
import org.openjdk.jmh.annotations.Setup;
32+
import org.openjdk.jmh.annotations.State;
33+
import org.openjdk.jmh.annotations.TearDown;
34+
35+
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
36+
import org.springframework.context.annotation.Bean;
37+
import org.springframework.context.annotation.Configuration;
38+
import org.springframework.data.annotation.Id;
39+
import org.springframework.data.jdbc.core.JdbcAggregateTemplate;
40+
import org.springframework.data.jdbc.repository.config.AbstractJdbcConfiguration;
41+
import org.springframework.data.relational.core.mapping.Embedded;
42+
import org.springframework.data.relational.core.mapping.Table;
43+
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
44+
import org.springframework.jdbc.datasource.ConnectionHolder;
45+
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
46+
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
47+
import org.springframework.transaction.support.TransactionSynchronizationManager;
48+
49+
/**
50+
* Benchmarks for Composite Ids in Spring Data JDBC.
51+
*
52+
* @author Mark Paluch
53+
*/
54+
@Testable
55+
public class CompositeIdBenchmarks extends BenchmarkSettings {
56+
57+
@Configuration
58+
static class BenchmarkConfiguration extends AbstractJdbcConfiguration {
59+
60+
@Bean
61+
NamedParameterJdbcTemplate namedParameterJdbcTemplate(DataSource dataSource) {
62+
return new NamedParameterJdbcTemplate(dataSource);
63+
}
64+
65+
@Bean
66+
DataSource dataSource() {
67+
68+
return new EmbeddedDatabaseBuilder() //
69+
.generateUniqueName(true) //
70+
.setType(EmbeddedDatabaseType.HSQL) //
71+
.setScriptEncoding("UTF-8") //
72+
.ignoreFailedDrops(true) //
73+
.addScript(
74+
"classpath:/org.springframework.data.jdbc.core/CompositeIdAggregateTemplateHsqlIntegrationTests-hsql.sql") //
75+
.build();
76+
}
77+
}
78+
79+
@State(Scope.Benchmark)
80+
public static class BenchmarkState {
81+
82+
AnnotationConfigApplicationContext context;
83+
JdbcAggregateTemplate template;
84+
NamedParameterJdbcTemplate named;
85+
AtomicLong l = new AtomicLong();
86+
SimpleEntity alpha;
87+
88+
@Setup
89+
public void setup() throws SQLException {
90+
91+
context = new AnnotationConfigApplicationContext();
92+
context.register(BenchmarkConfiguration.class);
93+
context.refresh();
94+
context.start();
95+
96+
template = context.getBean(JdbcAggregateTemplate.class);
97+
named = context.getBean(NamedParameterJdbcTemplate.class);
98+
DataSource dataSource = context.getBean(DataSource.class);
99+
100+
Connection connection = dataSource.getConnection();
101+
ConnectionHolder holder = new ConnectionHolder(connection, true);
102+
holder.setSynchronizedWithTransaction(true);
103+
TransactionSynchronizationManager.bindResource(dataSource, holder);
104+
105+
alpha = template.insert(new SimpleEntity(new WrappedPk(l.incrementAndGet()), "alpha"));
106+
}
107+
108+
@TearDown
109+
public void cleanup() {
110+
context.close();
111+
}
112+
}
113+
114+
@Benchmark
115+
public Object namedTemplate(BenchmarkState state) {
116+
return state.named.query("SELECT * FROM SIMPLE_ENTITY WHERE id = :id", Map.of("id", state.alpha.wrappedPk.id),
117+
(rs, rowNum) -> 1);
118+
}
119+
120+
@Benchmark
121+
public Object jdbcTemplate(BenchmarkState state) {
122+
return state.named.getJdbcOperations().query("SELECT * FROM SIMPLE_ENTITY WHERE id = " + state.alpha.wrappedPk.id,
123+
(rs, rowNum) -> 1);
124+
}
125+
126+
@Benchmark
127+
public Object baselineInsert(BenchmarkState state) {
128+
return state.template.insert(new BaselineEntity(state.l.incrementAndGet(), "alpha"));
129+
}
130+
131+
@Benchmark
132+
public Object loadBaselineEntity(BenchmarkState state) {
133+
return state.template.findById(state.alpha.wrappedPk, BaselineEntity.class);
134+
}
135+
136+
@Benchmark
137+
public Object insert(BenchmarkState state) {
138+
return state.template.insert(new SimpleEntity(new WrappedPk(state.l.incrementAndGet()), "alpha"));
139+
}
140+
141+
@Benchmark
142+
public Object loadSimpleEntity(BenchmarkState state) {
143+
return state.template.findById(state.alpha.wrappedPk, SimpleEntity.class);
144+
}
145+
146+
@Benchmark
147+
public Object saveAndLoadEntityWithList(BenchmarkState state) {
148+
149+
WithList entity = state.template.insert(new WithList(new WrappedPk(state.l.incrementAndGet()), "alpha",
150+
List.of(new Child("Romulus"), new Child("Remus"))));
151+
152+
assertThat(entity.wrappedPk).isNotNull() //
153+
.extracting(WrappedPk::id).isNotNull();
154+
155+
return state.template.findById(entity.wrappedPk, WithList.class);
156+
}
157+
158+
@Benchmark
159+
public Object saveAndLoadSimpleEntityWithEmbeddedPk(BenchmarkState state) {
160+
161+
SimpleEntityWithEmbeddedPk entity = state.template
162+
.insert(new SimpleEntityWithEmbeddedPk(new EmbeddedPk(state.l.incrementAndGet(), "x"), "alpha"));
163+
164+
return state.template.findById(entity.embeddedPk, SimpleEntityWithEmbeddedPk.class);
165+
}
166+
167+
@Benchmark
168+
public void deleteSingleSimpleEntityWithEmbeddedPk(BenchmarkState state) {
169+
170+
List<SimpleEntityWithEmbeddedPk> entities = (List<SimpleEntityWithEmbeddedPk>) state.template
171+
.insertAll(List.of(new SimpleEntityWithEmbeddedPk(new EmbeddedPk(23L, "x"), "alpha")));
172+
173+
state.template.delete(entities.get(0));
174+
}
175+
176+
@Benchmark
177+
public void deleteMultipleSimpleEntityWithEmbeddedPk(BenchmarkState state) {
178+
179+
List<SimpleEntityWithEmbeddedPk> entities = (List<SimpleEntityWithEmbeddedPk>) state.template
180+
.insertAll(List.of(new SimpleEntityWithEmbeddedPk(new EmbeddedPk(23L, "x"), "alpha"),
181+
new SimpleEntityWithEmbeddedPk(new EmbeddedPk(23L, "y"), "beta")));
182+
183+
state.template.deleteAll(List.of(entities.get(1), entities.get(0)));
184+
}
185+
186+
@Benchmark
187+
public void updateSingleSimpleEntityWithEmbeddedPk(BenchmarkState state) {
188+
189+
List<SimpleEntityWithEmbeddedPk> entities = (List<SimpleEntityWithEmbeddedPk>) state.template
190+
.insertAll(List.of(new SimpleEntityWithEmbeddedPk(new EmbeddedPk(23L, "x"), "alpha"),
191+
new SimpleEntityWithEmbeddedPk(new EmbeddedPk(23L, "y"), "beta"),
192+
new SimpleEntityWithEmbeddedPk(new EmbeddedPk(24L, "y"), "gamma")));
193+
194+
SimpleEntityWithEmbeddedPk updated = new SimpleEntityWithEmbeddedPk(new EmbeddedPk(23L, "x"), "ALPHA");
195+
state.template.save(updated);
196+
197+
state.template.deleteAll(SimpleEntityWithEmbeddedPk.class);
198+
}
199+
200+
private record WrappedPk(Long id) {
201+
}
202+
203+
@Table("SIMPLE_ENTITY")
204+
private record BaselineEntity( //
205+
@Id Long id, //
206+
String name //
207+
) {
208+
}
209+
210+
private record SimpleEntity( //
211+
@Id @Embedded(onEmpty = Embedded.OnEmpty.USE_NULL) WrappedPk wrappedPk, //
212+
String name //
213+
) {
214+
}
215+
216+
private record Child(String name) {
217+
}
218+
219+
private record WithList( //
220+
@Id @Embedded(onEmpty = Embedded.OnEmpty.USE_NULL) WrappedPk wrappedPk, //
221+
String name, List<Child> children) {
222+
}
223+
224+
private record EmbeddedPk(Long one, String two) {
225+
}
226+
227+
private record SimpleEntityWithEmbeddedPk( //
228+
@Id @Embedded(onEmpty = Embedded.OnEmpty.USE_NULL) EmbeddedPk embeddedPk, //
229+
String name //
230+
) {
231+
}
232+
233+
private record SingleReference( //
234+
@Id @Embedded(onEmpty = Embedded.OnEmpty.USE_NULL) EmbeddedPk embeddedPk, //
235+
String name, //
236+
Child child) {
237+
}
238+
239+
private record WithListAndCompositeId( //
240+
@Id @Embedded(onEmpty = Embedded.OnEmpty.USE_NULL) EmbeddedPk embeddedPk, //
241+
String name, //
242+
List<Child> child) {
243+
}
244+
245+
}

0 commit comments

Comments
 (0)