diff --git a/benchmark/relational/pom.xml b/benchmark/relational/pom.xml
index 3ac78eb..cdb42af 100644
--- a/benchmark/relational/pom.xml
+++ b/benchmark/relational/pom.xml
@@ -36,6 +36,11 @@
h2
+
+ org.postgresql
+ postgresql
+
+
org.mockito
mockito-core
diff --git a/benchmark/relational/readme.adoc b/benchmark/relational/readme.adoc
new file mode 100644
index 0000000..a188d6b
--- /dev/null
+++ b/benchmark/relational/readme.adoc
@@ -0,0 +1,35 @@
+= Benchmarks for relational data access with Spring Data
+
+This benchmark evaluates various options of relation data access in the Spring Data project family:
+
+- JDBC and Spring Data JDBC
+- JPA and Spring Data JPA
+
+The primary purpose of the benchmark is to help the team detect degradations in performance quickly or verify optimizations made in various areas of the libraries.
+It also helps justifying differences between numbers in rather clean room contexts (an embedded database) and scenarios that use a more realistic setup like a locally running database.
+That difference alone will help reasoning about the real-world impact of an optimization or degradation.
+
+== Benchmark model
+
+The benchmarks use a very simple model of a book with a title and pages attribute.
+We deliberately chose a simple model as the benchmarks are supposed to measure the overhead the Spring Data mapping and repository infrastructure adds on top of the raw JDBC and JPA alternatives.
+There are two major benchmark operations:
+
+1. Finding all books (8 items)
+2. Finding a single book by title.
+
+There are different flavors of those operations to measure the impact of different setups to execute them:
+
+- the effect of read-only transactions in the find all case
+- the difference between derived and declared queries in JPA
+
+== Infrastructure
+
+The benchmarks are run against the following databases:
+
+- In-memory H2
+- A locally running H2 (port 9092, database name `benchmark`, user `sa`, empty password)
+- A locally running Postgres (port 5432, database name `benchmark`, no user, no password)
+
+The settings can be adapted by tweaking corresponding `application-$database.properties` file in `src/main/resources`.
+
\ No newline at end of file
diff --git a/benchmark/relational/src/main/java/org/springframework/data/microbenchmark/Constants.java b/benchmark/relational/src/main/java/org/springframework/data/microbenchmark/Constants.java
deleted file mode 100644
index bce1ae9..0000000
--- a/benchmark/relational/src/main/java/org/springframework/data/microbenchmark/Constants.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright 2019 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.microbenchmark;
-
-/**
- * @author Oliver Drotbohm
- */
-public class Constants {
-
- public static final int NUMBER_OF_BOOKS = 8;
-}
diff --git a/benchmark/relational/src/main/java/org/springframework/data/microbenchmark/FixtureUtils.java b/benchmark/relational/src/main/java/org/springframework/data/microbenchmark/FixtureUtils.java
new file mode 100644
index 0000000..64c04a8
--- /dev/null
+++ b/benchmark/relational/src/main/java/org/springframework/data/microbenchmark/FixtureUtils.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2019 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.microbenchmark;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.context.ConfigurableApplicationContext;
+
+import lombok.experimental.UtilityClass;
+
+@UtilityClass
+public class FixtureUtils {
+
+ public static final int NUMBER_OF_BOOKS = 8;
+
+ public static ConfigurableApplicationContext createContext(Class> configuration, String api, String database) {
+
+ SpringApplication application = new SpringApplication();
+ application.addPrimarySources(Collections.singletonList(configuration));
+ application.setLazyInitialization(true);
+ application.setAdditionalProfiles(api, database);
+
+ System.out.println("Activating profiles: " + Arrays.asList(api, database).toString());
+
+ return application.run();
+ }
+}
diff --git a/benchmark/relational/src/main/java/org/springframework/data/microbenchmark/jdbc/JdbcBenchmark.java b/benchmark/relational/src/main/java/org/springframework/data/microbenchmark/jdbc/JdbcBenchmark.java
index 8ccc965..f9ae76d 100644
--- a/benchmark/relational/src/main/java/org/springframework/data/microbenchmark/jdbc/JdbcBenchmark.java
+++ b/benchmark/relational/src/main/java/org/springframework/data/microbenchmark/jdbc/JdbcBenchmark.java
@@ -15,50 +15,127 @@
*/
package org.springframework.data.microbenchmark.jdbc;
+import java.util.HashMap;
+import java.util.Set;
+import java.util.TreeSet;
+
import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Setup;
-import org.openjdk.jmh.annotations.TearDown;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.util.Optional;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.data.jdbc.core.convert.EntityRowMapper;
+import org.springframework.data.jdbc.core.convert.JdbcConverter;
+import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.microbenchmark.common.AbstractMicrobenchmark;
+import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
+import org.springframework.jdbc.core.JdbcOperations;
+import org.springframework.jdbc.core.RowMapper;
+
+import com.mockrunner.mock.jdbc.MockResultSet;
/**
+ * Benchmark for JDBC and Spring Data JDBC
+ *
* @author Oliver Drotbohm
*/
public class JdbcBenchmark extends AbstractMicrobenchmark {
- private JdbcFixture fixture;
+ private static final String BY_TITLE_SQL = "SELECT id, title, pages FROM Book where title = ?";
+
+ @Param({ "postgres", "h2-in-memory", "h2" }) String profile;
+
+ JdbcOperations operations;
+ RowMapper bookMapper;
+ EntityRowMapper bookEntityMapper;
+ JdbcBookRepository repository;
+
+ Set columns;
+ HashMap values;
@Setup
+ @SuppressWarnings("unchecked")
public void setUp() {
- this.fixture = new JdbcFixture();
+
+ JdbcFixture fixture = new JdbcFixture(profile);
+
+ this.bookMapper = fixture.getBookMapper();
+
+ ConfigurableApplicationContext context = fixture.getContext();
+
+ this.operations = context.getBean(JdbcOperations.class);
+ this.repository = context.getBean(JdbcBookRepository.class);
+
+ JdbcConverter converter = context.getBean(JdbcConverter.class);
+ JdbcMappingContext mappingContext = context.getBean(JdbcMappingContext.class);
+
+ RelationalPersistentEntity requiredPersistentEntity = (RelationalPersistentEntity) mappingContext
+ .getRequiredPersistentEntity(Book.class);
+
+ this.bookEntityMapper = new EntityRowMapper(requiredPersistentEntity, converter);
+
+ // ResultSet mock
+
+ this.columns = new TreeSet<>();
+ columns.add("id");
+ columns.add("title");
+ columns.add("pages");
+
+ this.values = new HashMap<>();
+ values.put("id", 1L);
+ values.put("title", "title0");
+ values.put("pages", 42L);
}
- @TearDown
- public void tearDown() {
- fixture.close();
+ @Benchmark
+ public void convertWithSpringData(Blackhole sink) throws Exception {
+
+ MockResultSet resultSet = new MockResultSet("book");
+ resultSet.addColumns(columns);
+ resultSet.addRow(values);
+ resultSet.next();
+
+ sink.consume(this.bookEntityMapper.mapRow(resultSet, 1));
}
@Benchmark
public void findByTitle(Blackhole sink) {
-
- Book book = fixture.getOperations().queryForObject("SELECT id, title, pages FROM Book where title = ?1",
- new Object[] { "title0" }, fixture.getBookMapper());
-
- sink.consume(book);
+ sink.consume(operations.queryForObject(BY_TITLE_SQL, new Object[] { "title0" }, bookMapper));
}
@Benchmark
public void findByTitleOptional(Blackhole sink) {
-
- Book book = fixture.getOperations().queryForObject("SELECT id, title, pages FROM Book where title = ?1",
- new Object[] { "title0" }, fixture.getBookMapper());
-
- sink.consume(Optional.of(book));
+ sink.consume(Optional.of(operations.queryForObject(BY_TITLE_SQL, new Object[] { "title0" }, bookMapper)));
}
@Benchmark
public void findAll(Blackhole sink) {
- sink.consume(fixture.getOperations().query("SELECT id, title, pages FROM Book", fixture.getBookMapper()));
+ sink.consume(operations.query("SELECT id, title, pages FROM Book", bookMapper));
+ }
+
+ @Benchmark
+ public void findAllWithSpringDataConversion(Blackhole sink) {
+ sink.consume(operations.query("SELECT id, title, pages FROM Book", bookEntityMapper));
+ }
+
+ @Benchmark
+ public void repositoryFindByTitle(Blackhole sink) {
+ sink.consume(repository.findByTitle("title0"));
+ }
+
+ @Benchmark
+ public void repositoryFindTransactionalByTitle(Blackhole sink) {
+ sink.consume(repository.findTransactionalByTitle("title0"));
+ }
+
+ @Benchmark
+ public void repositoryFindByTitleOptional(Blackhole sink) {
+ sink.consume(repository.findOptionalByTitle("title0"));
+ }
+
+ @Benchmark
+ public void repositoryFindAll(Blackhole sink) {
+ sink.consume(repository.findAll());
}
}
diff --git a/benchmark/relational/src/main/java/org/springframework/data/microbenchmark/jdbc/springdata/JdbcBookRepository.java b/benchmark/relational/src/main/java/org/springframework/data/microbenchmark/jdbc/JdbcBookRepository.java
similarity index 59%
rename from benchmark/relational/src/main/java/org/springframework/data/microbenchmark/jdbc/springdata/JdbcBookRepository.java
rename to benchmark/relational/src/main/java/org/springframework/data/microbenchmark/jdbc/JdbcBookRepository.java
index 89b02cf..faac854 100644
--- a/benchmark/relational/src/main/java/org/springframework/data/microbenchmark/jdbc/springdata/JdbcBookRepository.java
+++ b/benchmark/relational/src/main/java/org/springframework/data/microbenchmark/jdbc/JdbcBookRepository.java
@@ -13,22 +13,34 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.data.microbenchmark.jdbc.springdata;
+package org.springframework.data.microbenchmark.jdbc;
import java.util.Optional;
import org.springframework.data.jdbc.repository.query.Query;
-import org.springframework.data.microbenchmark.jdbc.Book;
import org.springframework.data.repository.CrudRepository;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
/**
+ * A repository for {@link Book} instances.
+ *
* @author Oliver Drotbohm
*/
-public interface JdbcBookRepository extends CrudRepository {
-
- @Query("SELECT id, title, pages FROM Book where title = :title")
+interface JdbcBookRepository extends CrudRepository {
+
+ static final String BY_TITLE = "SELECT id, title, pages FROM Book where title = :title";
+
+ @Transactional(propagation = Propagation.NOT_SUPPORTED)
+ Iterable findAll();
+
+ @Query(BY_TITLE)
Book findByTitle(String title);
+
+ @Transactional(readOnly = true)
+ @Query(BY_TITLE)
+ Book findTransactionalByTitle(String title);
- @Query("SELECT id, title, pages FROM Book where title = :title")
+ @Query(BY_TITLE)
Optional findOptionalByTitle(String title);
}
diff --git a/benchmark/relational/src/main/java/org/springframework/data/microbenchmark/jdbc/JdbcFixture.java b/benchmark/relational/src/main/java/org/springframework/data/microbenchmark/jdbc/JdbcFixture.java
index fb9097e..72e6b48 100644
--- a/benchmark/relational/src/main/java/org/springframework/data/microbenchmark/jdbc/JdbcFixture.java
+++ b/benchmark/relational/src/main/java/org/springframework/data/microbenchmark/jdbc/JdbcFixture.java
@@ -15,60 +15,86 @@
*/
package org.springframework.data.microbenchmark.jdbc;
+import java.lang.reflect.Field;
+
+import org.springframework.aop.framework.Advised;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.data.jdbc.core.JdbcAggregateTemplate;
+import org.springframework.data.jdbc.repository.support.SimpleJdbcRepository;
+import org.springframework.data.mapping.callback.EntityCallback;
+import org.springframework.data.mapping.callback.EntityCallbacks;
+import org.springframework.data.microbenchmark.FixtureUtils;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.util.ReflectionUtils;
+
import lombok.Getter;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.stream.IntStream;
-
-import org.springframework.boot.SpringApplication;
-import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.context.ConfigurableApplicationContext;
-import org.springframework.data.microbenchmark.Constants;
-import org.springframework.jdbc.core.JdbcOperations;
-import org.springframework.jdbc.core.RowMapper;
-
/**
+ * Test fixture for JDBC and Spring Data JDBC benchmarks.
+ *
* @author Oliver Drotbohm
*/
-public class JdbcFixture {
-
- private final @Getter JdbcOperations operations;
- private final @Getter RowMapper bookMapper;
- private final List books = new ArrayList<>();
+class JdbcFixture {
private final @Getter ConfigurableApplicationContext context;
+ private final @Getter RowMapper bookMapper;
- public JdbcFixture() {
+ JdbcFixture(String database) {
- SpringApplication application = new SpringApplication();
- application.addPrimarySources(Collections.singletonList(JdbcApplication.class));
- application.setLazyInitialization(true);
- application.setAdditionalProfiles("jdbc");
- this.context = application.run();
+ this.context = FixtureUtils.createContext(JdbcApplication.class, "jdbc", database);
- this.operations = context.getBean(JdbcOperations.class);
-
- // Schema
- this.operations.execute(
- "create table Book (id bigint not null auto_increment, title varchar(255), pages integer not null, primary key (id))");
-
- // Data
- IntStream.range(0, Constants.NUMBER_OF_BOOKS) //
- .mapToObj(it -> new Book(null, "title" + it, it)) //
- .peek(it -> books.add(it)) //
- .forEach(it -> operations.update("INSERT INTO Book VALUES (null, ?1, ?2)", it.getTitle(), it.getPages()));
+ // disableEntityCallbacks(context);
this.bookMapper = (rs, rowNum) -> new Book(rs.getLong("id"), rs.getString("title"), rs.getInt("pages"));
}
+
+ private static void disableEntityCallbacks(ApplicationContext context) {
+
+ JdbcBookRepository repository = context.getBean(JdbcBookRepository.class);
+
+ Field field = ReflectionUtils.findField(SimpleJdbcRepository.class, "entityOperations");
+ ReflectionUtils.makeAccessible(field);
+
+ try {
+ JdbcAggregateTemplate aggregateTemplate = (JdbcAggregateTemplate) ReflectionUtils.getField(field,
+ ((Advised) repository).getTargetSource().getTarget());
+
+ field = ReflectionUtils.findField(JdbcAggregateTemplate.class, "publisher");
+ ReflectionUtils.makeAccessible(field);
+ ReflectionUtils.setField(field, aggregateTemplate, NoOpApplicationEventPublisher.INSTANCE);
+
+ aggregateTemplate.setEntityCallbacks(NoOpEntityCallbacks.INSTANCE);
+
+ } catch (Exception o_O) {
+ throw new RuntimeException(o_O);
+ }
+ }
@SpringBootApplication
- static class JdbcApplication {
+ static class JdbcApplication {}
+ enum NoOpApplicationEventPublisher implements ApplicationEventPublisher {
+
+ INSTANCE;
+
+ @Override
+ public void publishEvent(Object event) {}
}
+
+ enum NoOpEntityCallbacks implements EntityCallbacks {
- public void close() {
- context.close();
+ INSTANCE;
+
+ @Override
+ public void addEntityCallback(EntityCallback> callback) {}
+
+ @Override
+ @SuppressWarnings("rawtypes")
+ public T callback(Class extends EntityCallback> callbackType, T entity, Object... args) {
+ return entity;
+ }
}
}
diff --git a/benchmark/relational/src/main/java/org/springframework/data/microbenchmark/jdbc/springdata/SpringDataJdbcBenchmark.java b/benchmark/relational/src/main/java/org/springframework/data/microbenchmark/jdbc/springdata/SpringDataJdbcBenchmark.java
deleted file mode 100644
index 09470ec..0000000
--- a/benchmark/relational/src/main/java/org/springframework/data/microbenchmark/jdbc/springdata/SpringDataJdbcBenchmark.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright 2019 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.microbenchmark.jdbc.springdata;
-
-import org.openjdk.jmh.annotations.Benchmark;
-import org.openjdk.jmh.annotations.Setup;
-import org.openjdk.jmh.infra.Blackhole;
-import org.springframework.data.microbenchmark.common.AbstractMicrobenchmark;
-import org.springframework.data.microbenchmark.jdbc.JdbcFixture;
-
-/**
- * @author Oliver Drotbohm
- */
-public class SpringDataJdbcBenchmark extends AbstractMicrobenchmark {
-
- private JdbcBookRepository repository;
-
- @Setup
- public void setUp() {
-
- JdbcFixture fixture = new JdbcFixture();
-
- this.repository = fixture.getContext().getBean(JdbcBookRepository.class);
- }
-
- @Benchmark
- public void findByTitle(Blackhole sink) {
-
- sink.consume(repository.findByTitle("title0"));
- }
-
- @Benchmark
- public void findByTitleOptional(Blackhole sink) {
-
- sink.consume(repository.findOptionalByTitle("title0"));
- }
-
- @Benchmark
- public void findAll(Blackhole sink) {
-
- sink.consume(repository.findAll());
- }
-}
diff --git a/benchmark/relational/src/main/java/org/springframework/data/microbenchmark/jpa/JpaBenchmark.java b/benchmark/relational/src/main/java/org/springframework/data/microbenchmark/jpa/JpaBenchmark.java
index 70f5abf..20b1314 100644
--- a/benchmark/relational/src/main/java/org/springframework/data/microbenchmark/jpa/JpaBenchmark.java
+++ b/benchmark/relational/src/main/java/org/springframework/data/microbenchmark/jpa/JpaBenchmark.java
@@ -17,6 +17,7 @@ package org.springframework.data.microbenchmark.jpa;
import java.util.Optional;
+import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
@@ -25,69 +26,99 @@ import javax.persistence.criteria.ParameterExpression;
import javax.persistence.criteria.Root;
import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.infra.Blackhole;
+import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.data.microbenchmark.common.AbstractMicrobenchmark;
/**
+ * Benchmarks for JPA and Spring Data JPA.
+ *
* @author Oliver Drotbohm
*/
public class JpaBenchmark extends AbstractMicrobenchmark {
- private JpaFixture fixture;
+ @Param({ "postgres", "h2-in-memory", "h2" })
+ String profile;
+
+ EntityManager em;
+ JpaBookRepository repository;
@Setup
public void setUp() {
- this.fixture = new JpaFixture();
+
+ ConfigurableApplicationContext context = new JpaFixture(profile).getContext();
+
+ this.em = context.getBean(EntityManager.class);
+ this.repository = context.getBean(JpaBookRepository.class);
}
@Benchmark
public void findByTitle(Blackhole sink) {
- fixture.withNonTransactionalEntityManager(em -> {
+ Query query = em.createQuery("select b from Book b where b.title = ?1");
+ query.setParameter(1, "title0");
- Query query = em.createQuery("select b from Book b where b.title = ?1");
- query.setParameter(1, "title0");
-
- sink.consume(query.getSingleResult());
- });
+ sink.consume(query.getSingleResult());
}
@Benchmark
public void findByTitleCriteria(Blackhole sink) {
- fixture.withNonTransactionalEntityManager(em -> {
+ CriteriaBuilder cb = em.getCriteriaBuilder();
+ CriteriaQuery q = cb.createQuery(Book.class);
+ Root c = q.from(Book.class);
- CriteriaBuilder cb = em.getCriteriaBuilder();
- CriteriaQuery q = cb.createQuery(Book.class);
- Root c = q.from(Book.class);
+ ParameterExpression parameter = cb.parameter(String.class);
- ParameterExpression parameter = cb.parameter(String.class);
+ TypedQuery query = em.createQuery(q.select(c).where(cb.equal(c.get("title"), parameter)));
+ query.setParameter(parameter, "title0");
- TypedQuery query = em.createQuery(q.select(c).where(cb.equal(c.get("title"), parameter)));
- query.setParameter(parameter, "title0");
-
- sink.consume(query.getSingleResult());
- });
+ sink.consume(query.getSingleResult());
}
@Benchmark
public void findByTitleOptional(Blackhole sink) {
- fixture.withNonTransactionalEntityManager(em -> {
+ Query query = em.createQuery("select b from Book b where b.title = ?1");
+ query.setParameter(1, "title0");
- Query query = em.createQuery("select b from Book b where b.title = ?1");
- query.setParameter(1, "title0");
-
- sink.consume(Optional.of(query.getSingleResult()));
- });
+ sink.consume(Optional.of(query.getSingleResult()));
}
@Benchmark
public void findAll(Blackhole sink) {
+ sink.consume(em.createQuery("select b from Book b").getResultList());
+ }
- fixture.withNonTransactionalEntityManager(em -> {
- sink.consume(em.createQuery("select b from Book b").getResultList());
- });
+ @Benchmark
+ public void repositoryFindByTitle(Blackhole sink) {
+ sink.consume(repository.findDerivedByTitle("title0"));
+ }
+
+ @Benchmark
+ public void repositoryFindTransactionalByTitle(Blackhole sink) {
+ sink.consume(repository.findTransactionalDerivedByTitle("title0"));
+ }
+
+ @Benchmark
+ public void repositoryFindByTitleDeclared(Blackhole sink) {
+ sink.consume(repository.findDeclaredByTitle("title0"));
+ }
+
+ @Benchmark
+ public void repositoryFindTransactionalByTitleDeclared(Blackhole sink) {
+ sink.consume(repository.findTransactionalDeclaredByTitle("title0"));
+ }
+
+ @Benchmark
+ public void repositoryFindByTitleOptional(Blackhole sink) {
+ sink.consume(repository.findOptionalDerivedByTitle("title0"));
+ }
+
+ @Benchmark
+ public void repositoryFindAll(Blackhole sink) {
+ sink.consume(repository.findAll());
}
}
diff --git a/benchmark/relational/src/main/java/org/springframework/data/microbenchmark/jpa/springdata/JpaBookRepository.java b/benchmark/relational/src/main/java/org/springframework/data/microbenchmark/jpa/JpaBookRepository.java
similarity index 65%
rename from benchmark/relational/src/main/java/org/springframework/data/microbenchmark/jpa/springdata/JpaBookRepository.java
rename to benchmark/relational/src/main/java/org/springframework/data/microbenchmark/jpa/JpaBookRepository.java
index 933e735..16627ce 100644
--- a/benchmark/relational/src/main/java/org/springframework/data/microbenchmark/jpa/springdata/JpaBookRepository.java
+++ b/benchmark/relational/src/main/java/org/springframework/data/microbenchmark/jpa/JpaBookRepository.java
@@ -13,23 +13,32 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.springframework.data.microbenchmark.jpa.springdata;
+package org.springframework.data.microbenchmark.jpa;
import java.util.Optional;
import org.springframework.data.jpa.repository.Query;
-import org.springframework.data.microbenchmark.jpa.Book;
import org.springframework.data.repository.CrudRepository;
+import org.springframework.transaction.annotation.Transactional;
/**
* @author Oliver Drotbohm
*/
-public interface JpaBookRepository extends CrudRepository {
+interface JpaBookRepository extends CrudRepository {
+
+ static final String BY_TITLE_JPQL = "select b from Book b where b.title = :title";
- @Query("select b from Book b where b.title = :title")
+ @Query(BY_TITLE_JPQL)
Book findDeclaredByTitle(String title);
+ @Transactional(readOnly = true)
+ @Query(BY_TITLE_JPQL)
+ Book findTransactionalDeclaredByTitle(String title);
+
Book findDerivedByTitle(String title);
+ @Transactional(readOnly = true)
+ Book findTransactionalDerivedByTitle(String title);
+
Optional findOptionalDerivedByTitle(String title);
}
diff --git a/benchmark/relational/src/main/java/org/springframework/data/microbenchmark/jpa/JpaFixture.java b/benchmark/relational/src/main/java/org/springframework/data/microbenchmark/jpa/JpaFixture.java
index 0a5f55b..055439c 100644
--- a/benchmark/relational/src/main/java/org/springframework/data/microbenchmark/jpa/JpaFixture.java
+++ b/benchmark/relational/src/main/java/org/springframework/data/microbenchmark/jpa/JpaFixture.java
@@ -17,45 +17,40 @@ package org.springframework.data.microbenchmark.jpa;
import lombok.Getter;
-import java.util.Collections;
import java.util.function.Consumer;
import java.util.stream.IntStream;
import javax.persistence.EntityManager;
-import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
-import org.springframework.data.microbenchmark.Constants;
+import org.springframework.data.microbenchmark.FixtureUtils;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
/**
+ * Test fixture for JPA and Spring Data JPA benchmarks.
+ *
* @author Oliver Drotbohm
*/
-public class JpaFixture {
+class JpaFixture {
private final @Getter ConfigurableApplicationContext context;
- public JpaFixture() {
-
- SpringApplication application = new SpringApplication();
- application.addPrimarySources(Collections.singletonList(JpaApplication.class));
- application.setAdditionalProfiles("jpa");
- application.setLazyInitialization(true);
-
- this.context = application.run();
+ JpaFixture(String database) {
+ this.context = FixtureUtils.createContext(JpaApplication.class, "jpa", database);
+
withTransactionalEntityManager(em -> {
- IntStream.range(0, Constants.NUMBER_OF_BOOKS) //
+ IntStream.range(0, FixtureUtils.NUMBER_OF_BOOKS) //
.mapToObj(it -> new Book(null, "title" + it, it)) //
.forEach(em::persist);
});
}
-
- public void withTransactionalEntityManager(Consumer consumer) {
+
+ private void withTransactionalEntityManager(Consumer consumer) {
PlatformTransactionManager manager = context.getBean(PlatformTransactionManager.class);
TransactionStatus status = manager.getTransaction(new DefaultTransactionDefinition());
@@ -69,15 +64,6 @@ public class JpaFixture {
em.close();
}
- public void withNonTransactionalEntityManager(Consumer consumer) {
-
- EntityManager em = context.getBean(EntityManager.class);
-
- consumer.accept(em);
-
- em.close();
- }
-
@SpringBootApplication
static class JpaApplication {}
}
diff --git a/benchmark/relational/src/main/java/org/springframework/data/microbenchmark/jpa/springdata/SpringDataJpaBenchmark.java b/benchmark/relational/src/main/java/org/springframework/data/microbenchmark/jpa/springdata/SpringDataJpaBenchmark.java
deleted file mode 100644
index db80d0d..0000000
--- a/benchmark/relational/src/main/java/org/springframework/data/microbenchmark/jpa/springdata/SpringDataJpaBenchmark.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright 2019 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.microbenchmark.jpa.springdata;
-
-import org.openjdk.jmh.annotations.Benchmark;
-import org.openjdk.jmh.annotations.Setup;
-import org.openjdk.jmh.infra.Blackhole;
-import org.springframework.data.microbenchmark.common.AbstractMicrobenchmark;
-import org.springframework.data.microbenchmark.jpa.JpaFixture;
-
-/**
- * @author Oliver Drotbohm
- */
-public class SpringDataJpaBenchmark extends AbstractMicrobenchmark {
-
- private JpaFixture fixture;
- private JpaBookRepository repository;
-
- @Setup
- public void setUp() {
-
- this.fixture = new JpaFixture();
- this.repository = fixture.getContext().getBean(JpaBookRepository.class);
- }
-
- @Benchmark
- public void findByTitle(Blackhole sink) {
-
- fixture.withNonTransactionalEntityManager(em -> {
- sink.consume(repository.findDerivedByTitle("title0"));
- });
- }
-
- @Benchmark
- public void findByTitleDeclared(Blackhole sink) {
-
- fixture.withNonTransactionalEntityManager(em -> {
- sink.consume(repository.findDeclaredByTitle("title0"));
- });
- }
-
- @Benchmark
- public void findByTitleOptional(Blackhole sink) {
-
- fixture.withNonTransactionalEntityManager(em -> {
- sink.consume(repository.findOptionalDerivedByTitle("title0"));
- });
- }
-
- @Benchmark
- public void findAll(Blackhole sink) {
-
- fixture.withNonTransactionalEntityManager(em -> {
- sink.consume(repository.findAll());
- });
- }
-}
diff --git a/benchmark/relational/src/main/resources/application-h2-in-memory.properties b/benchmark/relational/src/main/resources/application-h2-in-memory.properties
new file mode 100644
index 0000000..7eecee5
--- /dev/null
+++ b/benchmark/relational/src/main/resources/application-h2-in-memory.properties
@@ -0,0 +1,2 @@
+spring.datasource.platform=h2
+
diff --git a/benchmark/relational/src/main/resources/application-h2.properties b/benchmark/relational/src/main/resources/application-h2.properties
new file mode 100644
index 0000000..1e4cfa4
--- /dev/null
+++ b/benchmark/relational/src/main/resources/application-h2.properties
@@ -0,0 +1,6 @@
+spring.datasource.url=jdbc:h2:tcp://localhost:9092/~/benchmark
+spring.datasource.username=sa
+spring.datasource.password=
+spring.datasource.platform=h2
+spring.datasource.initialization-mode=always
+
diff --git a/benchmark/relational/src/main/resources/application-jpa.properties b/benchmark/relational/src/main/resources/application-jpa.properties
index 5836638..d7c7035 100644
--- a/benchmark/relational/src/main/resources/application-jpa.properties
+++ b/benchmark/relational/src/main/resources/application-jpa.properties
@@ -1,2 +1,2 @@
-spring.jpa.hibernate.ddl-auto=update
+spring.jpa.hibernate.ddl-auto=create-drop
spring.data.jdbc.repositories.enabled=false
diff --git a/benchmark/relational/src/main/resources/application-postgres.properties b/benchmark/relational/src/main/resources/application-postgres.properties
new file mode 100644
index 0000000..afd1d52
--- /dev/null
+++ b/benchmark/relational/src/main/resources/application-postgres.properties
@@ -0,0 +1,4 @@
+spring.datasource.url=jdbc:postgresql://localhost:5432/benchmark
+spring.datasource.platform=postgres
+spring.datasource.initialization-mode=always
+
diff --git a/benchmark/relational/src/main/resources/data-h2.sql b/benchmark/relational/src/main/resources/data-h2.sql
new file mode 100644
index 0000000..f2324d6
--- /dev/null
+++ b/benchmark/relational/src/main/resources/data-h2.sql
@@ -0,0 +1,8 @@
+INSERT INTO Book VALUES (null, 'title0', 0);
+INSERT INTO Book VALUES (null, 'title1', 1);
+INSERT INTO Book VALUES (null, 'title2', 2);
+INSERT INTO Book VALUES (null, 'title3', 3);
+INSERT INTO Book VALUES (null, 'title4', 4);
+INSERT INTO Book VALUES (null, 'title5', 5);
+INSERT INTO Book VALUES (null, 'title6', 6);
+INSERT INTO Book VALUES (null, 'title7', 7);
diff --git a/benchmark/relational/src/main/resources/data-postgres.sql b/benchmark/relational/src/main/resources/data-postgres.sql
new file mode 100644
index 0000000..0267a4c
--- /dev/null
+++ b/benchmark/relational/src/main/resources/data-postgres.sql
@@ -0,0 +1,8 @@
+INSERT INTO Book(title, pages) VALUES ('title0', 0);
+INSERT INTO Book(title, pages) VALUES ('title1', 1);
+INSERT INTO Book(title, pages) VALUES ('title2', 2);
+INSERT INTO Book(title, pages) VALUES ('title3', 3);
+INSERT INTO Book(title, pages) VALUES ('title4', 4);
+INSERT INTO Book(title, pages) VALUES ('title5', 5);
+INSERT INTO Book(title, pages) VALUES ('title6', 6);
+INSERT INTO Book(title, pages) VALUES ('title7', 7);
diff --git a/benchmark/relational/src/main/resources/schema-h2.sql b/benchmark/relational/src/main/resources/schema-h2.sql
new file mode 100644
index 0000000..fd90b96
--- /dev/null
+++ b/benchmark/relational/src/main/resources/schema-h2.sql
@@ -0,0 +1,7 @@
+drop table Book if exists;
+create table Book (
+ id bigint not null auto_increment,
+ title varchar(255),
+ pages integer not null,
+ primary key (id)
+);
diff --git a/benchmark/relational/src/main/resources/schema-postgres.sql b/benchmark/relational/src/main/resources/schema-postgres.sql
new file mode 100644
index 0000000..f143267
--- /dev/null
+++ b/benchmark/relational/src/main/resources/schema-postgres.sql
@@ -0,0 +1,6 @@
+drop table if exists Book;
+create table Book (
+ id serial primary key,
+ title varchar(255),
+ pages integer not null
+);
diff --git a/benchmark/support/src/main/java/org/springframework/data/microbenchmark/common/AbstractMicrobenchmark.java b/benchmark/support/src/main/java/org/springframework/data/microbenchmark/common/AbstractMicrobenchmark.java
index 70ae2a5..6791b78 100644
--- a/benchmark/support/src/main/java/org/springframework/data/microbenchmark/common/AbstractMicrobenchmark.java
+++ b/benchmark/support/src/main/java/org/springframework/data/microbenchmark/common/AbstractMicrobenchmark.java
@@ -31,10 +31,10 @@ import org.openjdk.jmh.annotations.Warmup;
* @author Mark Paluch
* @see Microbenchmark
*/
-@Warmup(iterations = 5)
+@Warmup(iterations = 10)
@Measurement(iterations = 10)
@Fork(value = 1, jvmArgs = { "-server", "-XX:+HeapDumpOnOutOfMemoryError", "-Xms1024m", "-Xmx1024m",
- "-XX:MaxDirectMemorySize=1024m" })
+ "-XX:MaxDirectMemorySize=1024m", "-noverify" })
@State(Scope.Thread)
@RunWith(Microbenchmark.class)
public abstract class AbstractMicrobenchmark {