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 {