Browse Source

Add Spring JPA/Hibernate implementation (#5770)

Adjust random world number generation to avoid duplicate values.
Otherwise, Hibernate would be "smart" and avoid querying the
database, which violates test requirements.

Update dependencies.  Remove seemingly-unused spring maven repos.

Remove mention of undeclared "postgres" profile.
Michael Hixson 5 years ago
parent
commit
1897885082

+ 21 - 0
frameworks/Java/spring/benchmark_config.json

@@ -24,6 +24,27 @@
       "notes": "",
       "notes": "",
       "versus": ""
       "versus": ""
     },
     },
+    "jpa": {
+      "db_url": "/db",
+      "query_url": "/queries?queries=",
+      "fortune_url": "/fortunes",
+      "update_url": "/updates?queries=",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Fullstack",
+      "database": "Postgres",
+      "framework": "spring",
+      "language": "Java",
+      "flavor": "None",
+      "orm": "Full",
+      "platform": "Tomcat",
+      "webserver": "None",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "spring-jpa",
+      "notes": "",
+      "versus": "spring"
+    },
     "mongo": {
     "mongo": {
       "db_url": "/db",
       "db_url": "/db",
       "query_url": "/queries?queries=",
       "query_url": "/queries?queries=",

+ 7 - 18
frameworks/Java/spring/pom.xml

@@ -13,14 +13,14 @@
   <parent>
   <parent>
     <groupId>org.springframework.boot</groupId>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-parent</artifactId>
     <artifactId>spring-boot-starter-parent</artifactId>
-    <version>2.2.0.M2</version>
+    <version>2.3.1.RELEASE</version>
   </parent>
   </parent>
 
 
   <properties>
   <properties>
     <maven.compiler.source>11</maven.compiler.source>
     <maven.compiler.source>11</maven.compiler.source>
     <maven.compiler.target>11</maven.compiler.target>
     <maven.compiler.target>11</maven.compiler.target>
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
-    <postgresql.version>42.2.5</postgresql.version>
+    <postgresql.version>42.2.14</postgresql.version>
   </properties>
   </properties>
 
 
   <dependencies>
   <dependencies>
@@ -28,6 +28,10 @@
       <groupId>org.springframework.boot</groupId>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-web</artifactId>
       <artifactId>spring-boot-starter-web</artifactId>
     </dependency>
     </dependency>
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-data-jpa</artifactId>
+    </dependency>
     <dependency>
     <dependency>
       <groupId>org.springframework.boot</groupId>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-data-mongodb</artifactId>
       <artifactId>spring-boot-starter-data-mongodb</artifactId>
@@ -47,21 +51,6 @@
     </dependency>
     </dependency>
   </dependencies>
   </dependencies>
 
 
-  <repositories>
-    <repository>
-      <id>spring-libs-snapshot</id>
-        <name>Spring Snapshots</name>
-        <url>https://repo.spring.io/libs-snapshot</url>
-      </repository>
-    </repositories>
-  <pluginRepositories>
-    <pluginRepository>
-      <id>spring-libs-snapshot</id>
-      <name>Spring Snapshots</name>
-      <url>https://repo.spring.io/libs-snapshot</url>
-    </pluginRepository>
-  </pluginRepositories>
-
   <build>
   <build>
     <plugins>
     <plugins>
       <plugin>
       <plugin>
@@ -71,7 +60,7 @@
       <plugin>
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-compiler-plugin</artifactId>
         <artifactId>maven-compiler-plugin</artifactId>
-        <version>3.8.0</version>
+        <version>3.8.1</version>
         <configuration>
         <configuration>
           <debug>false</debug>
           <debug>false</debug>
         </configuration>
         </configuration>

+ 10 - 0
frameworks/Java/spring/spring-jpa.dockerfile

@@ -0,0 +1,10 @@
+FROM maven:3.6.1-jdk-11-slim as maven
+WORKDIR /spring
+COPY src src
+COPY pom.xml pom.xml
+RUN mvn package -q
+
+FROM openjdk:11.0.3-jdk-slim
+WORKDIR /spring
+COPY --from=maven /spring/target/hello-spring-1.0-SNAPSHOT.jar app.jar
+CMD ["java", "-server", "-XX:+UseNUMA", "-XX:+UseParallelGC", "-Dlogging.level.root=OFF", "-jar", "app.jar", "--spring.profiles.active=jpa"]

+ 1 - 1
frameworks/Java/spring/spring.dockerfile

@@ -7,4 +7,4 @@ RUN mvn package -q
 FROM openjdk:11.0.3-jdk-slim
 FROM openjdk:11.0.3-jdk-slim
 WORKDIR /spring
 WORKDIR /spring
 COPY --from=maven /spring/target/hello-spring-1.0-SNAPSHOT.jar app.jar
 COPY --from=maven /spring/target/hello-spring-1.0-SNAPSHOT.jar app.jar
-CMD ["java", "-server", "-XX:+UseNUMA", "-XX:+UseParallelGC", "-Dlogging.level.root=OFF", "-jar", "app.jar", "--spring.profiles.active=jdbc,postgres"]
+CMD ["java", "-server", "-XX:+UseNUMA", "-XX:+UseParallelGC", "-Dlogging.level.root=OFF", "-jar", "app.jar", "--spring.profiles.active=jdbc"]

+ 1 - 1
frameworks/Java/spring/src/main/java/hello/App.java

@@ -18,7 +18,7 @@ public class App {
     }
     }
 
 
     @Bean
     @Bean
-    @Profile("jdbc")
+    @Profile({"jdbc", "jpa"})
     public DataSource datasource(DataSourceProperties dataSourceProperties) {
     public DataSource datasource(DataSourceProperties dataSourceProperties) {
         HikariDataSource dataSource = dataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
         HikariDataSource dataSource = dataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
         dataSource.setMaximumPoolSize(Runtime.getRuntime().availableProcessors() * 2);
         dataSource.setMaximumPoolSize(Runtime.getRuntime().availableProcessors() * 2);

+ 11 - 0
frameworks/Java/spring/src/main/java/hello/Config.java

@@ -0,0 +1,11 @@
+package hello;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
+
+@Configuration
+@EnableJpaRepositories("hello.jpa")
+@EnableMongoRepositories("hello.repository")
+public class Config {
+}

+ 36 - 16
frameworks/Java/spring/src/main/java/hello/controller/HelloController.java

@@ -2,10 +2,10 @@ package hello.controller;
 
 
 import static java.util.Comparator.comparing;
 import static java.util.Comparator.comparing;
 
 
-import java.util.Arrays;
 import java.util.List;
 import java.util.List;
 import java.util.Map;
 import java.util.Map;
 import java.util.concurrent.ThreadLocalRandom;
 import java.util.concurrent.ThreadLocalRandom;
+import java.util.stream.IntStream;
 
 
 import hello.model.Fortune;
 import hello.model.Fortune;
 import hello.model.World;
 import hello.model.World;
@@ -37,28 +37,37 @@ public final class HelloController {
 
 
   @RequestMapping("/db")
   @RequestMapping("/db")
   @ResponseBody
   @ResponseBody
-
   World db() {
   World db() {
-    return randomWorld();
+    return dbRepository.getWorld(randomWorldNumber());
   }
   }
 
 
   @RequestMapping("/queries")
   @RequestMapping("/queries")
   @ResponseBody
   @ResponseBody
   World[] queries(@RequestParam String queries) {
   World[] queries(@RequestParam String queries) {
-    var worlds = new World[parseQueryCount(queries)];
-    Arrays.setAll(worlds, i -> randomWorld());
-    return worlds;
+    return randomWorldNumbers()
+        .mapToObj(dbRepository::getWorld)
+        .limit(parseQueryCount(queries))
+        .toArray(World[]::new);
   }
   }
 
 
   @RequestMapping("/updates")
   @RequestMapping("/updates")
   @ResponseBody
   @ResponseBody
   World[] updates(@RequestParam String queries) {
   World[] updates(@RequestParam String queries) {
-    var worlds = new World[parseQueryCount(queries)];
-    Arrays.setAll(worlds, i -> randomWorld());
-    for (var world : worlds) {
-      dbRepository.updateWorld(world, randomWorldNumber());
-    }
-    return worlds;
+    return randomWorldNumbers()
+        .mapToObj(dbRepository::getWorld)
+        .map(world -> {
+          // Ensure that the new random number is not equal to the old one.
+          // That would cause the JPA-based implementation to avoid sending the
+          // UPDATE query to the database, which would violate the test
+          // requirements.
+          int newRandomNumber;
+          do {
+            newRandomNumber = randomWorldNumber();
+          } while (newRandomNumber == world.randomnumber);
+          return dbRepository.updateWorld(world, newRandomNumber);
+        })
+        .limit(parseQueryCount(queries))
+        .toArray(World[]::new);
   }
   }
 
 
   @RequestMapping("/fortunes")
   @RequestMapping("/fortunes")
@@ -71,12 +80,23 @@ public final class HelloController {
     return fortunes;
     return fortunes;
   }
   }
 
 
-  private World randomWorld() {
-    return dbRepository.getWorld(randomWorldNumber());
-  }
+  private static final int MIN_WORLD_NUMBER = 1;
+  private static final int MAX_WORLD_NUMBER_PLUS_ONE = 10_001;
 
 
   private static int randomWorldNumber() {
   private static int randomWorldNumber() {
-    return 1 + ThreadLocalRandom.current().nextInt(10000);
+    return ThreadLocalRandom
+        .current()
+        .nextInt(MIN_WORLD_NUMBER, MAX_WORLD_NUMBER_PLUS_ONE);
+  }
+
+  private static IntStream randomWorldNumbers() {
+    return ThreadLocalRandom
+        .current()
+        .ints(MIN_WORLD_NUMBER, MAX_WORLD_NUMBER_PLUS_ONE)
+        // distinct() allows us to avoid using Hibernate's first-level cache in
+        // the JPA-based implementation.  Using a cache like that would bypass
+        // querying the database, which would violate the test requirements.
+        .distinct();
   }
   }
 
 
   private static int parseQueryCount(String textValue) {
   private static int parseQueryCount(String textValue) {

+ 11 - 0
frameworks/Java/spring/src/main/java/hello/jpa/FortuneRepository.java

@@ -0,0 +1,11 @@
+package hello.jpa;
+
+import hello.model.Fortune;
+import org.springframework.context.annotation.Profile;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Component;
+
+@Component
+@Profile("jpa")
+public interface FortuneRepository extends JpaRepository<Fortune, Integer> {
+}

+ 40 - 0
frameworks/Java/spring/src/main/java/hello/jpa/JpaDbRepository.java

@@ -0,0 +1,40 @@
+package hello.jpa;
+
+import hello.model.Fortune;
+import hello.model.World;
+import hello.repository.DbRepository;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.annotation.Profile;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+@Component
+@Profile("jpa")
+public class JpaDbRepository implements DbRepository {
+    private final Logger log = LoggerFactory.getLogger(getClass());
+    private final WorldRepository worldRepository;
+    private final FortuneRepository fortuneRepository;
+
+    public JpaDbRepository(WorldRepository worldRepository, FortuneRepository fortuneRepository) {
+        this.worldRepository = worldRepository;
+        this.fortuneRepository = fortuneRepository;
+    }
+
+    @Override
+    public World getWorld(int id) {
+        return worldRepository.findById(id).orElse(null);
+    }
+
+    @Override
+    public World updateWorld(World world, int randomNumber) {
+        world.randomnumber = randomNumber;
+        return worldRepository.save(world);
+    }
+
+    @Override
+    public List<Fortune> fortunes() {
+        return fortuneRepository.findAll();
+    }
+}

+ 11 - 0
frameworks/Java/spring/src/main/java/hello/jpa/WorldRepository.java

@@ -0,0 +1,11 @@
+package hello.jpa;
+
+import hello.model.World;
+import org.springframework.context.annotation.Profile;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Component;
+
+@Component
+@Profile("jpa")
+public interface WorldRepository extends JpaRepository<World, Integer> {
+}

+ 5 - 0
frameworks/Java/spring/src/main/java/hello/model/Fortune.java

@@ -1,14 +1,19 @@
 package hello.model;
 package hello.model;
 
 
+import javax.persistence.Entity;
 import org.springframework.data.annotation.Id;
 import org.springframework.data.annotation.Id;
 import org.springframework.data.mongodb.core.mapping.Document;
 import org.springframework.data.mongodb.core.mapping.Document;
 
 
 @Document
 @Document
+@Entity
 public final class Fortune {
 public final class Fortune {
   @Id
   @Id
+  @javax.persistence.Id
   public int id;
   public int id;
   public String message;
   public String message;
 
 
+  protected Fortune() {}
+
   public Fortune(int id, String message) {
   public Fortune(int id, String message) {
     this.id = id;
     this.id = id;
     this.message = message;
     this.message = message;

+ 5 - 0
frameworks/Java/spring/src/main/java/hello/model/World.java

@@ -1,17 +1,22 @@
 package hello.model;
 package hello.model;
 
 
+import javax.persistence.Entity;
 import org.springframework.data.annotation.Id;
 import org.springframework.data.annotation.Id;
 import org.springframework.data.mongodb.core.mapping.Document;
 import org.springframework.data.mongodb.core.mapping.Document;
 import org.springframework.data.mongodb.core.mapping.Field;
 import org.springframework.data.mongodb.core.mapping.Field;
 
 
 @Document
 @Document
+@Entity
 public final class World {
 public final class World {
 
 
   @Id
   @Id
+  @javax.persistence.Id
   public int id;
   public int id;
   @Field("randomNumber")
   @Field("randomNumber")
   public int randomnumber;
   public int randomnumber;
 
 
+  protected World() {}
+
   public World(int id, int randomnumber) {
   public World(int id, int randomnumber) {
     this.id = id;
     this.id = id;
     this.randomnumber = randomnumber;
     this.randomnumber = randomnumber;

+ 7 - 1
frameworks/Java/spring/src/main/resources/application.yml

@@ -1,6 +1,6 @@
 ---
 ---
 spring:
 spring:
-  profiles: jdbc
+  profiles: jdbc,jpa
   datasource:
   datasource:
     url: jdbc:postgresql://${database.host}:${database.port}/${database.name}
     url: jdbc:postgresql://${database.host}:${database.port}/${database.name}
     username: ${database.username}
     username: ${database.username}
@@ -13,6 +13,12 @@ database:
   username: benchmarkdbuser
   username: benchmarkdbuser
   password: benchmarkdbpass
   password: benchmarkdbpass
 
 
+---
+spring:
+  profiles: jpa
+  jpa:
+    database-platform: org.hibernate.dialect.PostgreSQLDialect
+
 ---
 ---
 spring:
 spring:
   profiles: mongo
   profiles: mongo