Skip to content

Commit cfe681a

Browse files
committed
Add AOT repository benchmarks.
See #3830
1 parent 027a797 commit cfe681a

File tree

8 files changed

+314
-17
lines changed

8 files changed

+314
-17
lines changed

pom.xml

+17
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,23 @@
5454

5555

5656
<profiles>
57+
<profile>
58+
<id>jmh</id>
59+
<dependencies>
60+
<dependency>
61+
<groupId>com.github.mp911de.microbenchmark-runner</groupId>
62+
<artifactId>microbenchmark-runner-junit5</artifactId>
63+
<version>0.4.0.RELEASE</version>
64+
<scope>test</scope>
65+
</dependency>
66+
</dependencies>
67+
<repositories>
68+
<repository>
69+
<id>jitpack.io</id>
70+
<url>https://jitpack.io</url>
71+
</repository>
72+
</repositories>
73+
</profile>
5774
<profile>
5875
<id>hibernate-70-snapshots</id>
5976
<properties>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
/*
2+
* Copyright 2024-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.jpa.benchmark;
17+
18+
import jakarta.persistence.EntityManager;
19+
import jakarta.persistence.EntityManagerFactory;
20+
import jakarta.persistence.Query;
21+
22+
import java.util.List;
23+
import java.util.Properties;
24+
import java.util.Set;
25+
26+
import org.hibernate.jpa.HibernatePersistenceProvider;
27+
import org.junit.platform.commons.annotation.Testable;
28+
import org.openjdk.jmh.annotations.Benchmark;
29+
import org.openjdk.jmh.annotations.Fork;
30+
import org.openjdk.jmh.annotations.Level;
31+
import org.openjdk.jmh.annotations.Measurement;
32+
import org.openjdk.jmh.annotations.Scope;
33+
import org.openjdk.jmh.annotations.Setup;
34+
import org.openjdk.jmh.annotations.State;
35+
import org.openjdk.jmh.annotations.TearDown;
36+
import org.openjdk.jmh.annotations.Timeout;
37+
import org.openjdk.jmh.annotations.Warmup;
38+
39+
import org.springframework.aot.test.generate.TestGenerationContext;
40+
import org.springframework.core.test.tools.TestCompiler;
41+
import org.springframework.data.domain.Sort;
42+
import org.springframework.data.jpa.benchmark.model.Person;
43+
import org.springframework.data.jpa.benchmark.model.Profile;
44+
import org.springframework.data.jpa.benchmark.repository.PersonRepository;
45+
import org.springframework.data.jpa.repository.aot.JpaRepositoryContributor;
46+
import org.springframework.data.jpa.repository.aot.TestJpaAotRepositoryContext;
47+
import org.springframework.data.jpa.repository.support.JpaRepositoryFactory;
48+
import org.springframework.data.projection.ProjectionFactory;
49+
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
50+
import org.springframework.data.repository.core.RepositoryMetadata;
51+
import org.springframework.data.repository.core.support.RepositoryComposition;
52+
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
53+
import org.springframework.data.repository.query.ValueExpressionDelegate;
54+
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
55+
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
56+
import org.springframework.util.ObjectUtils;
57+
58+
/**
59+
* @author Christoph Strobl
60+
* @author Mark Paluch
61+
*/
62+
@Testable
63+
@Fork(1)
64+
@Warmup(time = 1, iterations = 3)
65+
@Measurement(time = 1, iterations = 3)
66+
@Timeout(time = 2)
67+
public class AotRepositoryQueryMethodBenchmarks {
68+
69+
private static final String PERSON_FIRSTNAME = "first";
70+
private static final String COLUMN_PERSON_FIRSTNAME = "firstname";
71+
72+
@State(Scope.Benchmark)
73+
public static class BenchmarkParameters {
74+
75+
public static Class<?> aot;
76+
public static TestJpaAotRepositoryContext<PersonRepository> repositoryContext = new TestJpaAotRepositoryContext<>(
77+
PersonRepository.class, null);
78+
79+
EntityManager entityManager;
80+
RepositoryComposition.RepositoryFragments fragments;
81+
PersonRepository repositoryProxy;
82+
83+
@Setup(Level.Iteration)
84+
public void doSetup() {
85+
86+
createEntityManager();
87+
88+
if (!entityManager.getTransaction().isActive()) {
89+
90+
if (ObjectUtils.nullSafeEquals(
91+
entityManager.createNativeQuery("SELECT COUNT(*) FROM person", Integer.class).getSingleResult(),
92+
Integer.valueOf(0))) {
93+
94+
entityManager.getTransaction().begin();
95+
96+
Profile generalProfile = new Profile("general");
97+
Profile sdUserProfile = new Profile("sd-user");
98+
99+
entityManager.persist(generalProfile);
100+
entityManager.persist(sdUserProfile);
101+
102+
Person person = new Person(PERSON_FIRSTNAME, "last");
103+
person.setProfiles(Set.of(generalProfile, sdUserProfile));
104+
entityManager.persist(person);
105+
entityManager.getTransaction().commit();
106+
}
107+
}
108+
109+
if (this.aot == null) {
110+
111+
TestGenerationContext generationContext = new TestGenerationContext(PersonRepository.class);
112+
113+
new JpaRepositoryContributor(repositoryContext, entityManager.getEntityManagerFactory())
114+
.contribute(generationContext);
115+
116+
TestCompiler.forSystem().withCompilerOptions("-parameters").with(generationContext).compile(compiled -> {
117+
118+
try {
119+
this.aot = compiled.getClassLoader().loadClass(PersonRepository.class.getName() + "Impl__Aot");
120+
} catch (Exception e) {
121+
throw new RuntimeException(e);
122+
}
123+
});
124+
}
125+
126+
try {
127+
RepositoryFactoryBeanSupport.FragmentCreationContext creationContext = getCreationContext(repositoryContext);
128+
fragments = RepositoryComposition.RepositoryFragments
129+
.just(aot.getConstructor(EntityManager.class, RepositoryFactoryBeanSupport.FragmentCreationContext.class)
130+
.newInstance(entityManager, creationContext));
131+
132+
this.repositoryProxy = createRepository(fragments);
133+
} catch (Exception e) {
134+
throw new RuntimeException(e);
135+
}
136+
}
137+
138+
private RepositoryFactoryBeanSupport.FragmentCreationContext getCreationContext(
139+
TestJpaAotRepositoryContext<?> repositoryContext) {
140+
141+
RepositoryFactoryBeanSupport.FragmentCreationContext creationContext = new RepositoryFactoryBeanSupport.FragmentCreationContext() {
142+
@Override
143+
public RepositoryMetadata getRepositoryMetadata() {
144+
return repositoryContext.getRepositoryInformation();
145+
}
146+
147+
@Override
148+
public ValueExpressionDelegate getValueExpressionDelegate() {
149+
return ValueExpressionDelegate.create();
150+
}
151+
152+
@Override
153+
public ProjectionFactory getProjectionFactory() {
154+
return new SpelAwareProxyProjectionFactory();
155+
}
156+
};
157+
158+
return creationContext;
159+
}
160+
161+
@TearDown(Level.Iteration)
162+
public void doTearDown() {
163+
entityManager.close();
164+
}
165+
166+
private void createEntityManager() {
167+
168+
LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
169+
factoryBean.setPersistenceUnitName("benchmark");
170+
factoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
171+
factoryBean.setPersistenceProviderClass(HibernatePersistenceProvider.class);
172+
factoryBean.setPersistenceXmlLocation("classpath*:META-INF/persistence-jmh.xml");
173+
factoryBean.setMappingResources("classpath*:META-INF/orm-jmh.xml");
174+
175+
Properties properties = new Properties();
176+
properties.put("jakarta.persistence.jdbc.url", "jdbc:h2:mem:test");
177+
properties.put("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
178+
properties.put("hibernate.hbm2ddl.auto", "update");
179+
properties.put("hibernate.xml_mapping_enabled", "false");
180+
181+
factoryBean.setJpaProperties(properties);
182+
factoryBean.afterPropertiesSet();
183+
184+
EntityManagerFactory entityManagerFactory = factoryBean.getObject();
185+
entityManager = entityManagerFactory.createEntityManager();
186+
}
187+
188+
public PersonRepository createRepository(RepositoryComposition.RepositoryFragments fragments) {
189+
JpaRepositoryFactory repositoryFactory = new JpaRepositoryFactory(entityManager);
190+
return repositoryFactory.getRepository(PersonRepository.class, fragments);
191+
}
192+
193+
}
194+
195+
protected PersonRepository doCreateRepository(EntityManager entityManager) {
196+
JpaRepositoryFactory repositoryFactory = new JpaRepositoryFactory(entityManager);
197+
return repositoryFactory.getRepository(PersonRepository.class);
198+
}
199+
200+
@Benchmark
201+
public PersonRepository repositoryBootstrap(BenchmarkParameters parameters) {
202+
return parameters.createRepository(parameters.fragments);
203+
}
204+
205+
@Benchmark
206+
public List<Person> baselineEntityManagerHQLQuery(BenchmarkParameters parameters) {
207+
208+
Query query = parameters.entityManager
209+
.createQuery("SELECT p FROM org.springframework.data.jpa.benchmark.model.Person p WHERE p.firstname = ?1");
210+
query.setParameter(1, PERSON_FIRSTNAME);
211+
212+
return query.getResultList();
213+
}
214+
215+
@Benchmark
216+
public Long baselineEntityManagerCount(BenchmarkParameters parameters) {
217+
218+
Query query = parameters.entityManager.createQuery(
219+
"SELECT COUNT(*) FROM org.springframework.data.jpa.benchmark.model.Person p WHERE p.firstname = ?1");
220+
query.setParameter(1, PERSON_FIRSTNAME);
221+
222+
return (Long) query.getSingleResult();
223+
}
224+
225+
@Benchmark
226+
public List<Person> derivedFinderMethod(BenchmarkParameters parameters) {
227+
return parameters.repositoryProxy.findAllByFirstname(PERSON_FIRSTNAME);
228+
}
229+
230+
/*@Benchmark
231+
public List<IPersonProjection> derivedFinderMethodWithInterfaceProjection(BenchmarkParameters parameters) {
232+
return parameters.repositoryProxy.findAllAndProjectToInterfaceByFirstname(PERSON_FIRSTNAME);
233+
} */
234+
235+
@Benchmark
236+
public List<Person> stringBasedQuery(BenchmarkParameters parameters) {
237+
return parameters.repositoryProxy.findAllWithAnnotatedQueryByFirstname(PERSON_FIRSTNAME);
238+
}
239+
240+
@Benchmark
241+
public List<Person> stringBasedQueryDynamicSort(BenchmarkParameters parameters) {
242+
return parameters.repositoryProxy.findAllWithAnnotatedQueryByFirstname(PERSON_FIRSTNAME,
243+
Sort.by(COLUMN_PERSON_FIRSTNAME));
244+
}
245+
246+
@Benchmark
247+
public List<Person> stringBasedNativeQuery(BenchmarkParameters parameters) {
248+
return parameters.repositoryProxy.findAllWithNativeQueryByFirstname(PERSON_FIRSTNAME);
249+
}
250+
251+
@Benchmark
252+
public Long derivedCount(BenchmarkParameters parameters) {
253+
return parameters.repositoryProxy.countByFirstname(PERSON_FIRSTNAME);
254+
}
255+
256+
@Benchmark
257+
public Long stringBasedCount(BenchmarkParameters parameters) {
258+
return parameters.repositoryProxy.countWithAnnotatedQueryByFirstname(PERSON_FIRSTNAME);
259+
}
260+
}

spring-data-jpa/src/jmh/java/org/springframework/data/jpa/benchmark/RepositoryFinderBenchmarks.java renamed to spring-data-jpa/src/jmh/java/org/springframework/data/jpa/benchmark/RepositoryQueryMethodBenchmarks.java

+15-8
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
import org.openjdk.jmh.annotations.Warmup;
4242

4343
import org.springframework.data.domain.Sort;
44-
import org.springframework.data.jpa.benchmark.model.IPersonProjection;
4544
import org.springframework.data.jpa.benchmark.model.Person;
4645
import org.springframework.data.jpa.benchmark.model.Profile;
4746
import org.springframework.data.jpa.benchmark.repository.PersonRepository;
@@ -52,13 +51,14 @@
5251

5352
/**
5453
* @author Christoph Strobl
54+
* @author Mark Paluch
5555
*/
5656
@Testable
5757
@Fork(1)
58-
@Warmup(time = 2, iterations = 3)
59-
@Measurement(time = 2)
58+
@Warmup(time = 1, iterations = 3)
59+
@Measurement(time = 1, iterations = 3)
6060
@Timeout(time = 2)
61-
public class RepositoryFinderBenchmarks {
61+
public class RepositoryQueryMethodBenchmarks {
6262

6363
private static final String PERSON_FIRSTNAME = "first";
6464
private static final String COLUMN_PERSON_FIRSTNAME = "firstname";
@@ -125,10 +125,16 @@ private void createEntityManager() {
125125
entityManager = entityManagerFactory.createEntityManager();
126126
}
127127

128-
PersonRepository createRepository() {
128+
public PersonRepository createRepository() {
129129
JpaRepositoryFactory repositoryFactory = new JpaRepositoryFactory(entityManager);
130130
return repositoryFactory.getRepository(PersonRepository.class);
131131
}
132+
133+
}
134+
135+
protected PersonRepository doCreateRepository(EntityManager entityManager) {
136+
JpaRepositoryFactory repositoryFactory = new JpaRepositoryFactory(entityManager);
137+
return repositoryFactory.getRepository(PersonRepository.class);
132138
}
133139

134140
@Benchmark
@@ -173,10 +179,10 @@ public List<Person> derivedFinderMethod(BenchmarkParameters parameters) {
173179
return parameters.repositoryProxy.findAllByFirstname(PERSON_FIRSTNAME);
174180
}
175181

176-
@Benchmark
182+
/*@Benchmark
177183
public List<IPersonProjection> derivedFinderMethodWithInterfaceProjection(BenchmarkParameters parameters) {
178184
return parameters.repositoryProxy.findAllAndProjectToInterfaceByFirstname(PERSON_FIRSTNAME);
179-
}
185+
} */
180186

181187
@Benchmark
182188
public List<Person> stringBasedQuery(BenchmarkParameters parameters) {
@@ -185,7 +191,8 @@ public List<Person> stringBasedQuery(BenchmarkParameters parameters) {
185191

186192
@Benchmark
187193
public List<Person> stringBasedQueryDynamicSort(BenchmarkParameters parameters) {
188-
return parameters.repositoryProxy.findAllWithAnnotatedQueryByFirstname(PERSON_FIRSTNAME, Sort.by(COLUMN_PERSON_FIRSTNAME));
194+
return parameters.repositoryProxy.findAllWithAnnotatedQueryByFirstname(PERSON_FIRSTNAME,
195+
Sort.by(COLUMN_PERSON_FIRSTNAME));
189196
}
190197

191198
@Benchmark

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/aot/JpaRepositoryContributor.java

+10-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.springframework.data.jpa.repository.aot;
1717

1818
import jakarta.persistence.EntityManager;
19+
import jakarta.persistence.EntityManagerFactory;
1920

2021
import java.lang.reflect.Method;
2122

@@ -68,10 +69,18 @@ public JpaRepositoryContributor(AotRepositoryContext repositoryContext) {
6869
AotMetamodel amm = new AotMetamodel(repositoryContext.getResolvedTypes());
6970

7071
this.persistenceProvider = PersistenceProvider.fromEntityManagerFactory(amm.getEntityManagerFactory());
71-
this.queriesFactory = new QueriesFactory(amm, amm.getEntityManagerFactory());
72+
this.queriesFactory = new QueriesFactory(amm.getEntityManagerFactory(), amm);
7273
this.entityGraphLookup = new EntityGraphLookup(amm.getEntityManagerFactory());
7374
}
7475

76+
public JpaRepositoryContributor(AotRepositoryContext repositoryContext, EntityManagerFactory entityManagerFactory) {
77+
super(repositoryContext);
78+
79+
this.persistenceProvider = PersistenceProvider.fromEntityManagerFactory(entityManagerFactory);
80+
this.queriesFactory = new QueriesFactory(entityManagerFactory);
81+
this.entityGraphLookup = new EntityGraphLookup(entityManagerFactory);
82+
}
83+
7584
@Override
7685
protected void customizeClass(RepositoryInformation information, AotRepositoryFragmentMetadata metadata,
7786
TypeSpec.Builder builder) {

0 commit comments

Comments
 (0)