From 757318b5cd213dc312abf6bcb4a78f11a11eab75 Mon Sep 17 00:00:00 2001 From: Oliver Drotbohm Date: Tue, 13 Aug 2019 09:59:54 +0200 Subject: [PATCH] #123 - Benchmark overhaul. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unified benchmarks into one for JDBC and JPA. They're now both testing the data access with the plain infrastructure (JdbcTemplate and EntityManager) plus interactions via Spring Data repositories on top of that. Added build profiles to run the same benchmarks against an in-memory H2, a locally running H2 and a locally running Postgres. See the readme for setup instructions. The JdbcFixture contains code to optionally disable event publication for the JDBC operation. Uncomment the line invoking disableEntityCallbacks(…). Changed the benchmarks to use 10 warmup iterations instead of 5 to make sure we're at stable numbers once the measurement happens. --- benchmark/relational/pom.xml | 5 + benchmark/relational/readme.adoc | 35 ++++++ .../data/microbenchmark/Constants.java | 24 ---- .../data/microbenchmark/FixtureUtils.java | 42 +++++++ .../microbenchmark/jdbc/JdbcBenchmark.java | 111 +++++++++++++++--- .../{springdata => }/JdbcBookRepository.java | 24 +++- .../data/microbenchmark/jdbc/JdbcFixture.java | 100 ++++++++++------ .../springdata/SpringDataJdbcBenchmark.java | 56 --------- .../data/microbenchmark/jpa/JpaBenchmark.java | 85 +++++++++----- .../{springdata => }/JpaBookRepository.java | 17 ++- .../data/microbenchmark/jpa/JpaFixture.java | 34 ++---- .../springdata/SpringDataJpaBenchmark.java | 70 ----------- .../application-h2-in-memory.properties | 2 + .../main/resources/application-h2.properties | 6 + .../main/resources/application-jpa.properties | 2 +- .../resources/application-postgres.properties | 4 + .../relational/src/main/resources/data-h2.sql | 8 ++ .../src/main/resources/data-postgres.sql | 8 ++ .../src/main/resources/schema-h2.sql | 7 ++ .../src/main/resources/schema-postgres.sql | 6 + .../common/AbstractMicrobenchmark.java | 4 +- 21 files changed, 382 insertions(+), 268 deletions(-) create mode 100644 benchmark/relational/readme.adoc delete mode 100644 benchmark/relational/src/main/java/org/springframework/data/microbenchmark/Constants.java create mode 100644 benchmark/relational/src/main/java/org/springframework/data/microbenchmark/FixtureUtils.java rename benchmark/relational/src/main/java/org/springframework/data/microbenchmark/jdbc/{springdata => }/JdbcBookRepository.java (59%) delete mode 100644 benchmark/relational/src/main/java/org/springframework/data/microbenchmark/jdbc/springdata/SpringDataJdbcBenchmark.java rename benchmark/relational/src/main/java/org/springframework/data/microbenchmark/jpa/{springdata => }/JpaBookRepository.java (65%) delete mode 100644 benchmark/relational/src/main/java/org/springframework/data/microbenchmark/jpa/springdata/SpringDataJpaBenchmark.java create mode 100644 benchmark/relational/src/main/resources/application-h2-in-memory.properties create mode 100644 benchmark/relational/src/main/resources/application-h2.properties create mode 100644 benchmark/relational/src/main/resources/application-postgres.properties create mode 100644 benchmark/relational/src/main/resources/data-h2.sql create mode 100644 benchmark/relational/src/main/resources/data-postgres.sql create mode 100644 benchmark/relational/src/main/resources/schema-h2.sql create mode 100644 benchmark/relational/src/main/resources/schema-postgres.sql 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 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 {