Browse Source

Add new benchmarks for spring-webflux using rxjava and r2dbc (#4314)

David Kiss 6 năm trước cách đây
mục cha
commit
b8706f5c85
24 tập tin đã thay đổi với 509 bổ sung186 xóa
  1. 13 9
      frameworks/Java/spring-webflux/README.md
  2. 27 4
      frameworks/Java/spring-webflux/benchmark_config.json
  3. 36 2
      frameworks/Java/spring-webflux/pom.xml
  4. 2 2
      frameworks/Java/spring-webflux/spring-webflux-jdbc.dockerfile
  5. 1 1
      frameworks/Java/spring-webflux/spring-webflux-mongo.dockerfile
  6. 2 2
      frameworks/Java/spring-webflux/spring-webflux-pgclient.dockerfile
  7. 10 0
      frameworks/Java/spring-webflux/spring-webflux-rxjdbc.dockerfile
  8. 2 2
      frameworks/Java/spring-webflux/spring-webflux.dockerfile
  9. 8 30
      frameworks/Java/spring-webflux/src/main/java/benchmark/App.java
  10. 22 0
      frameworks/Java/spring-webflux/src/main/java/benchmark/config/JdbcConfig.java
  11. 59 0
      frameworks/Java/spring-webflux/src/main/java/benchmark/config/R2dbcConfig.java
  12. 3 2
      frameworks/Java/spring-webflux/src/main/java/benchmark/config/ReactiveMongoConfig.java
  13. 29 0
      frameworks/Java/spring-webflux/src/main/java/benchmark/config/RxJdbcConfig.java
  14. 0 33
      frameworks/Java/spring-webflux/src/main/java/benchmark/controller/BenchmarkController.java
  15. 0 83
      frameworks/Java/spring-webflux/src/main/java/benchmark/controller/ReactiveController.java
  16. 8 0
      frameworks/Java/spring-webflux/src/main/java/benchmark/model/Fortune.java
  17. 5 3
      frameworks/Java/spring-webflux/src/main/java/benchmark/model/World.java
  18. 2 2
      frameworks/Java/spring-webflux/src/main/java/benchmark/repository/JdbcDbRepository.java
  19. 2 2
      frameworks/Java/spring-webflux/src/main/java/benchmark/repository/PgClientDbRepository.java
  20. 56 0
      frameworks/Java/spring-webflux/src/main/java/benchmark/repository/R2dbcDbRepository.java
  21. 60 0
      frameworks/Java/spring-webflux/src/main/java/benchmark/repository/RxJdbcDbRepository.java
  22. 104 0
      frameworks/Java/spring-webflux/src/main/java/benchmark/web/WebfluxHandler.java
  23. 37 0
      frameworks/Java/spring-webflux/src/main/java/benchmark/web/WebfluxRouter.java
  24. 21 9
      frameworks/Java/spring-webflux/src/main/resources/application.yml

+ 13 - 9
frameworks/Java/spring-webflux/README.md

@@ -6,41 +6,45 @@ Netty is used for the async web server, with nearly everything configured with d
 
 A fixed thread pool of size equals to the number of database connections is used to run all the blocking code (jdbc database accesses) to not block netty's event loop.
 
-For postgresql access, there are two implementations.
+For postgresql access, there are four implementations.
 * [JdbcDbRepository](src/main/java/benchmark/JdbcDbRepository.java) is using JdbcTemplate.
 * [PgClientDbRepository](src/main/java/benchmark/PgClientDbRepository.java) is using reactive-pg-client
+* [RxJava2DbRepository](src/main/java/benchmark/RxJava2DbRepository.java) is using rxjava2-jdbc
+* [R2dbcDbRepository](src/main/java/benchmark/R2dbcDbRepository.java) is using r2dbc-postgresql
 For mongoDB access, spring-data-mongodb with reactive support is used. See [MongoDbRepository](src/main/java/benchmark/MongoDbRepository.java)
 
 ### Plaintext Test
 
-* [Plaintext test source](src/main/java/benchmark/Controller/BenchmarkController.java)
+* [Plaintext test source](src/main/java/benchmark/web/WebfluxRouter.java)
 
 ### JSON Serialization Test
 
-* [JSON test source](src/main/java/benchmark/Controller/BenchmarkController.java)
+* [JSON test source](src/main/java/benchmark/web/WebfluxRouterr.java)
 
 ### Database Query Test
 
-* [Query test source](src/main/java/benchmark/Controller/ReactiveController.java)
+* [Query test source](src/main/java/benchmark/web/WebfluxRouter.java)
 
 ### Database Queries Test
 
-* [Queries test source](src/main/java/benchmark/Controller/ReactiveController.java)
+* [Queries test source](src/main/java/benchmark/web/WebfluxRouter.java)
 
 ### Database Update Test
 
-* [Update test source](src/main/java/benchmark/Controller/ReactiveController.java)
+* [Update test source](src/main/java/benchmark/web/WebfluxRouter.java)
 
 ### Template rendering Test
 
-* [Template rendering test source](src/main/java/benchmark/Controller/ReactiveController.java)
+* [Template rendering test source](src/main/java/benchmark/web/WebfluxRouter.java)
 
 ## Versions
 
 * [Java OpenJDK 10](http://openjdk.java.net/)
-* [Spring boot 2.0.5](https://spring.io/projects/spring-boot)
-* [Spring data mongodb 2.0.9](https://projects.spring.io/spring-data-mongodb/)
+* [Spring boot 2.1.0.RELEASE](https://spring.io/projects/spring-boot)
+* [Spring data mongodb 2.1.0.RELEASE](https://projects.spring.io/spring-data-mongodb/)
 * [reactive-pg-client 0.10.6](https://github.com/reactiverse/reactive-pg-client)
+* [rxjava2-jdbc 0.2.0](https://github.com/davidmoten/rxjava2-jdbc)
+* [r2dbc-postgresql 1.0.0.BUILD-SNAPSHOT](https://github.com/r2dbc/r2dbc-postgresql)
 
 ## Test URLs
 

+ 27 - 4
frameworks/Java/spring-webflux/benchmark_config.json

@@ -2,12 +2,14 @@
   "framework": "spring-webflux",
   "tests": [{
     "default": {
-      "json_url": "/json",
-      "plaintext_url": "/plaintext",
+      "db_url": "/db",
+      "query_url": "/queries?queries=",
+      "fortune_url": "/fortunes",
+      "update_url": "/updates?queries=",
       "port": 8080,
       "approach": "Realistic",
       "classification": "Fullstack",
-      "database": "None",
+      "database": "Postgres",
       "framework": "spring",
       "language": "Java",
       "flavor": "None",
@@ -16,7 +18,7 @@
       "webserver": "None",
       "os": "Linux",
       "database_os": "Linux",
-      "display_name": "spring-webflux",
+      "display_name": "spring-webflux-r2dbc",
       "notes": "",
       "versus": "spring"
     },
@@ -62,6 +64,27 @@
       "notes": "",
       "versus": "spring"
     },
+    "rxjdbc": {
+      "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": "Micro",
+      "platform": "Netty",
+      "webserver": "None",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "spring-webflux-rxjdbc",
+      "notes": "",
+      "versus": "spring"
+    },
     "jdbc": {
       "db_url": "/db",
       "query_url": "/queries?queries=",

+ 36 - 2
frameworks/Java/spring-webflux/pom.xml

@@ -8,12 +8,12 @@
 
     <groupId>benchmark</groupId>
     <artifactId>spring-webflux-benchmark</artifactId>
-    <version>1.0-SNAPSHOT</version>
+    <version>1.1-SNAPSHOT</version>
 
     <parent>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-parent</artifactId>
-        <version>2.0.5.RELEASE</version>
+        <version>2.1.0.RELEASE</version>
     </parent>
 
     <properties>
@@ -22,6 +22,8 @@
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <postgresql.version>42.2.5</postgresql.version>
         <pgclient.version>0.10.6</pgclient.version>
+        <rxjava2-jdbc.version>0.2.0</rxjava2-jdbc.version>
+        <r2dbc-postgresql.version>1.0.0.BUILD-SNAPSHOT</r2dbc-postgresql.version>
     </properties>
 
     <dependencies>
@@ -51,9 +53,41 @@
             <artifactId>reactive-pg-client</artifactId>
             <version>${pgclient.version}</version>
         </dependency>
+        <dependency>
+            <groupId>com.github.davidmoten</groupId>
+            <artifactId>rxjava2-jdbc</artifactId>
+            <version>${rxjava2-jdbc.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>io.r2dbc</groupId>
+            <artifactId>r2dbc-postgresql</artifactId>
+            <version>${r2dbc-postgresql.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.data</groupId>
+            <artifactId>spring-data-r2dbc</artifactId>
+            <version>1.0.0.BUILD-SNAPSHOT</version>
+        </dependency>
     </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>
+        <finalName>${project.artifactId}</finalName>
         <plugins>
             <plugin>
                 <groupId>org.springframework.boot</groupId>

+ 2 - 2
frameworks/Java/spring-webflux/spring-webflux-jdbc.dockerfile

@@ -6,5 +6,5 @@ RUN mvn package -q
 
 FROM openjdk:10-jre-slim
 WORKDIR /spring
-COPY --from=maven /spring/target/spring-webflux-benchmark-1.0-SNAPSHOT.jar app.jar
-CMD ["java", "-server", "-XX:+UseNUMA", "-XX:+UseParallelGC", "-Dlogging.level.root=OFF", "-jar", "app.jar", "--spring.profiles.active=jdbc"]
+COPY --from=maven /spring/target/spring-webflux-benchmark.jar app.jar
+CMD ["java", "-server", "-XX:+UseNUMA", "-XX:+UseParallelGC", "-Dlogging.level.root=OFF", "-jar", "app.jar", "--spring.profiles.active=jdbc,postgres"]

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

@@ -6,5 +6,5 @@ RUN mvn package -q
 
 FROM openjdk:10-jre-slim
 WORKDIR /spring
-COPY --from=maven /spring/target/spring-webflux-benchmark-1.0-SNAPSHOT.jar app.jar
+COPY --from=maven /spring/target/spring-webflux-benchmark.jar app.jar
 CMD ["java", "-server", "-XX:+UseNUMA", "-XX:+UseParallelGC", "-Dlogging.level.root=OFF", "-jar", "app.jar", "--spring.profiles.active=mongo"]

+ 2 - 2
frameworks/Java/spring-webflux/spring-webflux-pgclient.dockerfile

@@ -6,5 +6,5 @@ RUN mvn package -q
 
 FROM openjdk:10-jre-slim
 WORKDIR /spring
-COPY --from=maven /spring/target/spring-webflux-benchmark-1.0-SNAPSHOT.jar app.jar
-CMD ["java", "-server", "-XX:+UseNUMA", "-XX:+UseParallelGC", "-Dlogging.level.root=OFF", "-jar", "app.jar", "--spring.profiles.active=pgclient"]
+COPY --from=maven /spring/target/spring-webflux-benchmark.jar app.jar
+CMD ["java", "-server", "-XX:+UseNUMA", "-XX:+UseParallelGC", "-Dlogging.level.root=OFF", "-jar", "app.jar", "--spring.profiles.active=pgclient,postgres"]

+ 10 - 0
frameworks/Java/spring-webflux/spring-webflux-rxjdbc.dockerfile

@@ -0,0 +1,10 @@
+FROM maven:3.5.3-jdk-10-slim as maven
+WORKDIR /spring
+COPY src src
+COPY pom.xml pom.xml
+RUN mvn package -q
+
+FROM openjdk:10-jre-slim
+WORKDIR /spring
+COPY --from=maven /spring/target/spring-webflux-benchmark.jar app.jar
+CMD ["java", "-server", "-XX:+UseNUMA", "-XX:+UseParallelGC", "-Dlogging.level.root=OFF", "-jar", "app.jar", "--spring.profiles.active=rxjdbc,postgres"]

+ 2 - 2
frameworks/Java/spring-webflux/spring-webflux.dockerfile

@@ -6,5 +6,5 @@ RUN mvn package -q
 
 FROM openjdk:10-jre-slim
 WORKDIR /spring
-COPY --from=maven /spring/target/spring-webflux-benchmark-1.0-SNAPSHOT.jar app.jar
-CMD ["java", "-server", "-XX:+UseNUMA", "-XX:+UseParallelGC", "-Dlogging.level.root=OFF", "-jar", "app.jar"]
+COPY --from=maven /spring/target/spring-webflux-benchmark.jar app.jar
+CMD ["java", "-server", "-XX:+UseNUMA", "-XX:+UseParallelGC", "-Dlogging.level.root=OFF", "-jar", "app.jar", "--spring.profiles.active=r2dbc,postgres"]

+ 8 - 30
frameworks/Java/spring-webflux/src/main/java/benchmark/App.java

@@ -1,34 +1,31 @@
 package benchmark;
 
-import com.samskivert.mustache.Mustache;
-import com.zaxxer.hikari.HikariDataSource;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration;
 import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
-import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
-import org.springframework.boot.autoconfigure.mustache.MustacheResourceTemplateLoader;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.boot.web.reactive.result.view.MustacheViewResolver;
 import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Profile;
-import org.springframework.data.mongodb.repository.config.EnableReactiveMongoRepositories;
 import org.springframework.scheduling.annotation.EnableScheduling;
 import org.springframework.web.reactive.config.EnableWebFlux;
 import org.springframework.web.reactive.config.ViewResolverRegistry;
 import org.springframework.web.reactive.config.WebFluxConfigurer;
-import org.springframework.web.reactive.result.view.ViewResolver;
 import reactor.core.scheduler.Scheduler;
 import reactor.core.scheduler.Schedulers;
 
-import javax.sql.DataSource;
 import java.util.concurrent.Executors;
 
-@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
+@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class, MongoReactiveDataAutoConfiguration.class })
 @EnableWebFlux
 @EnableScheduling
 @EnableConfigurationProperties
 public class App implements WebFluxConfigurer {
 
+    @Autowired
+    private MustacheViewResolver mustacheViewResolver;
+
     public static void main(String[] args) {
         SpringApplication.run(App.class, args);
     }
@@ -48,28 +45,9 @@ public class App implements WebFluxConfigurer {
         return Schedulers.fromExecutor(Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2));
     }
 
-    @Bean
-    public ViewResolver mustacheViewResolver() {
-        String prefix = "classpath:/templates/";
-        String suffix = ".mustache";
-        Mustache.TemplateLoader loader = new MustacheResourceTemplateLoader(prefix, suffix);
-        MustacheViewResolver mustacheViewResolver = new MustacheViewResolver(Mustache.compiler().withLoader(loader));
-        mustacheViewResolver.setPrefix(prefix);
-        mustacheViewResolver.setSuffix(suffix);
-        return mustacheViewResolver;
-    }
-
     @Override
     public void configureViewResolvers(ViewResolverRegistry registry) {
-        registry.viewResolver(mustacheViewResolver());
+        registry.viewResolver(mustacheViewResolver);
     }
 
-    @Bean
-    @Profile("jdbc")
-    public DataSource datasource(DataSourceProperties dataSourceProperties) {
-        HikariDataSource dataSource = (HikariDataSource) dataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
-        dataSource.setMaximumPoolSize(Runtime.getRuntime().availableProcessors() * 2);
-
-        return dataSource;
-    }
-}
+}

+ 22 - 0
frameworks/Java/spring-webflux/src/main/java/benchmark/config/JdbcConfig.java

@@ -0,0 +1,22 @@
+package benchmark.config;
+
+import com.zaxxer.hikari.HikariDataSource;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+
+import javax.sql.DataSource;
+
+@Configuration
+@Profile("jdbc")
+public class JdbcConfig {
+
+    @Bean
+    public DataSource datasource(DataSourceProperties dataSourceProperties) {
+        HikariDataSource dataSource = dataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
+        dataSource.setMaximumPoolSize(Runtime.getRuntime().availableProcessors() * 2);
+
+        return dataSource;
+    }
+}

+ 59 - 0
frameworks/Java/spring-webflux/src/main/java/benchmark/config/R2dbcConfig.java

@@ -0,0 +1,59 @@
+package benchmark.config;
+
+import io.r2dbc.postgresql.PostgresqlConnectionConfiguration;
+import io.r2dbc.postgresql.PostgresqlConnectionFactory;
+import io.r2dbc.spi.ConnectionFactory;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.springframework.data.r2dbc.function.DatabaseClient;
+
+@Configuration
+@Profile("r2dbc")
+@ConfigurationProperties(prefix = "database")
+public class R2dbcConfig {
+    private String name;
+    private String host;
+    private int port;
+    private String username;
+    private String password;
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public void setHost(String host) {
+        this.host = host;
+    }
+
+    public void setPort(int port) {
+        this.port = port;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    @Bean
+    public PostgresqlConnectionFactory connectionFactory() {
+        PostgresqlConnectionConfiguration configuration = PostgresqlConnectionConfiguration
+                .builder()
+                .host(host)
+                .port(port)
+                .database(name)
+                .username(username)
+                .password(password)
+                .build();
+        return new PostgresqlConnectionFactory(configuration);
+    }
+
+    @Bean
+    public DatabaseClient databaseClient(ConnectionFactory connectionFactory) {
+        return DatabaseClient.create(connectionFactory);
+    }
+}

+ 3 - 2
frameworks/Java/spring-webflux/src/main/java/benchmark/config/ReactiveMongoConfig.java

@@ -2,8 +2,8 @@ package benchmark.config;
 
 import com.mongodb.reactivestreams.client.MongoClient;
 import com.mongodb.reactivestreams.client.MongoClients;
+import org.slf4j.LoggerFactory;
 import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Profile;
@@ -19,8 +19,9 @@ public class ReactiveMongoConfig extends AbstractReactiveMongoConfiguration {
     private String url;
     private String name;
 
-    @Override
+    @Bean
     public MongoClient reactiveMongoClient() {
+        LoggerFactory.getLogger(getClass()).info("Connecting to mongo url: {}/{}", url, name);
         return MongoClients.create(url);
     }
 

+ 29 - 0
frameworks/Java/spring-webflux/src/main/java/benchmark/config/RxJdbcConfig.java

@@ -0,0 +1,29 @@
+package benchmark.config;
+
+import org.davidmoten.rx.jdbc.ConnectionProvider;
+import org.davidmoten.rx.jdbc.Database;
+import org.davidmoten.rx.jdbc.pool.NonBlockingConnectionPool;
+import org.davidmoten.rx.jdbc.pool.Pools;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+
+import java.sql.SQLException;
+
+@Configuration
+@Profile("rxjdbc")
+public class RxJdbcConfig {
+    @Bean
+    public Database database(DataSourceProperties dsProps) throws SQLException {
+        NonBlockingConnectionPool pool =
+                Pools.nonBlocking()
+                        .maxPoolSize(Runtime.getRuntime().availableProcessors() * 2)
+                        .connectionProvider(ConnectionProvider.from(dsProps.getUrl(), dsProps.getUsername(), dsProps.getPassword()))
+                        .build();
+
+        Database db = Database.from(pool);
+
+        return db;
+    }
+}

+ 0 - 33
frameworks/Java/spring-webflux/src/main/java/benchmark/controller/BenchmarkController.java

@@ -1,33 +0,0 @@
-package benchmark.controller;
-
-import org.springframework.jdbc.core.JdbcTemplate;
-import org.springframework.stereotype.Controller;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.ResponseBody;
-import org.springframework.web.reactive.result.view.Rendering;
-import reactor.core.publisher.Mono;
-import reactor.core.scheduler.Scheduler;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ThreadLocalRandom;
-
-import static java.util.Comparator.comparing;
-
-@Controller
-public final class BenchmarkController {
-
-    @GetMapping(value = "/plaintext", produces = "text/plain")
-    @ResponseBody
-    public Mono<String> plaintext() {
-        return Mono.just("Hello, World!");
-    }
-
-    @GetMapping(value = "/json", produces = "application/json")
-    @ResponseBody
-    public Mono<Map<String, String>> json() {
-        return Mono.just(Map.of("message", "Hello, World!"));
-    }
-}

+ 0 - 83
frameworks/Java/spring-webflux/src/main/java/benchmark/controller/ReactiveController.java

@@ -1,83 +0,0 @@
-package benchmark.controller;
-
-import benchmark.model.Fortune;
-import benchmark.model.World;
-import benchmark.repository.DbRepository;
-import org.springframework.context.annotation.Profile;
-import org.springframework.stereotype.Controller;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.ResponseBody;
-import org.springframework.web.reactive.result.view.Rendering;
-import reactor.core.publisher.Flux;
-import reactor.core.publisher.Mono;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.ThreadLocalRandom;
-
-import static java.util.Comparator.comparing;
-
-@Controller()
-@Profile(value = {"jdbc", "pgclient", "mongo"})
-public final class ReactiveController {
-
-    private final DbRepository dbRepository;
-
-    public ReactiveController(DbRepository dbRepository) {
-        this.dbRepository = dbRepository;
-    }
-
-    @GetMapping(value = "/db", produces = "application/json")
-    @ResponseBody
-    public Mono<World> db() {
-        return dbRepository.getWorld(randomWorldNumber());
-    }
-
-    @GetMapping(value = "/queries", produces = "application/json")
-    @ResponseBody
-    public Mono<List<World>> queries(@RequestParam String queries) {
-        Mono<World>[] worlds = new Mono[parseQueryCount(queries)];
-        Arrays.setAll(worlds, i -> db());
-
-        return Flux.merge(worlds).collectList();
-    }
-
-    @GetMapping(value = "/updates", produces = "application/json")
-    @ResponseBody
-    public Mono<List<World>> updates(@RequestParam String queries) {
-        Mono<World>[] worlds = new Mono[parseQueryCount(queries)];
-
-        Arrays.setAll(worlds, i -> dbRepository.findAndUpdateWorld(randomWorldNumber(), randomWorldNumber()));
-
-        return Flux.merge(worlds).collectList();
-    }
-
-    @GetMapping(value = "/fortunes")
-    public Rendering fortunes() {
-        Mono<List<Fortune>> result = dbRepository.fortunes().collectList().flatMap(fortunes -> {
-            fortunes.add(new Fortune(0, "Additional fortune added at request time."));
-            fortunes.sort(comparing(fortune -> fortune.message));
-            return Mono.just(fortunes);
-        });
-
-        return Rendering.view("fortunes").modelAttribute("fortunes", result).build();
-    }
-
-    protected int randomWorldNumber() {
-        return 1 + ThreadLocalRandom.current().nextInt(10000);
-    }
-
-    protected int parseQueryCount(String textValue) {
-        if (textValue == null) {
-            return 1;
-        }
-        int parsedValue;
-        try {
-            parsedValue = Integer.parseInt(textValue);
-        } catch (NumberFormatException e) {
-            return 1;
-        }
-        return Math.min(500, Math.max(1, parsedValue));
-    }
-}

+ 8 - 0
frameworks/Java/spring-webflux/src/main/java/benchmark/model/Fortune.java

@@ -13,4 +13,12 @@ public final class Fortune {
         this.id = id;
         this.message = message;
     }
+
+    public int getId() {
+        return id;
+    }
+
+    public String getMessage() {
+        return message;
+    }
 }

+ 5 - 3
frameworks/Java/spring-webflux/src/main/java/benchmark/model/World.java

@@ -2,16 +2,18 @@ package benchmark.model;
 
 import org.springframework.data.annotation.Id;
 import org.springframework.data.mongodb.core.mapping.Document;
+import org.springframework.data.mongodb.core.mapping.Field;
 
 @Document
 public final class World {
 
     @Id
     public int id;
-    public int randomNumber;
+    @Field("randomNumber")
+    public int randomnumber;
 
-    public World(int id, int randomNumber) {
+    public World(int id, int randomnumber) {
         this.id = id;
-        this.randomNumber = randomNumber;
+        this.randomnumber = randomnumber;
     }
 }

+ 2 - 2
frameworks/Java/spring-webflux/src/main/java/benchmark/repository/JdbcDbRepository.java

@@ -38,7 +38,7 @@ public class JdbcDbRepository implements DbRepository {
         return Mono.fromCallable(() -> {
             jdbcTemplate.update(
                     "UPDATE world SET randomnumber = ? WHERE id = ?",
-                    world.randomNumber,
+                    world.randomnumber,
                     world.id);
             return world;
         }).subscribeOn(scheduler);
@@ -47,7 +47,7 @@ public class JdbcDbRepository implements DbRepository {
     @Override
     public Mono<World> findAndUpdateWorld(int id, int randomNumber) {
         return getWorld(id).flatMap(world -> {
-            world.randomNumber = randomNumber;
+            world.randomnumber = randomNumber;
             return updateWorld(world);
         });
     }

+ 2 - 2
frameworks/Java/spring-webflux/src/main/java/benchmark/repository/PgClientDbRepository.java

@@ -41,7 +41,7 @@ public class PgClientDbRepository implements DbRepository {
 
     private Mono<World> updateWorld(World world) {
         return Mono.create(sink -> {
-            pgClients.getOne().preparedQuery("UPDATE world SET randomnumber = $1 WHERE id = $2", Tuple.of(world.randomNumber, world.id), ar -> {
+            pgClients.getOne().preparedQuery("UPDATE world SET randomnumber = $1 WHERE id = $2", Tuple.of(world.randomnumber, world.id), ar -> {
                 if (ar.failed()) {
                     sink.error(ar.cause());
                 } else {
@@ -54,7 +54,7 @@ public class PgClientDbRepository implements DbRepository {
     @Override
     public Mono<World> findAndUpdateWorld(int id, int randomNumber) {
         return getWorld(id).flatMap(world -> {
-            world.randomNumber = randomNumber;
+            world.randomnumber = randomNumber;
             return updateWorld(world);
         });
     }

+ 56 - 0
frameworks/Java/spring-webflux/src/main/java/benchmark/repository/R2dbcDbRepository.java

@@ -0,0 +1,56 @@
+package benchmark.repository;
+
+import benchmark.model.Fortune;
+import benchmark.model.World;
+import org.springframework.context.annotation.Profile;
+import org.springframework.data.r2dbc.function.DatabaseClient;
+import org.springframework.stereotype.Component;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+@Component
+@Profile("r2dbc")
+public class R2dbcDbRepository implements DbRepository {
+    private final DatabaseClient databaseClient;
+
+    public R2dbcDbRepository(DatabaseClient databaseClient) {
+        this.databaseClient = databaseClient;
+    }
+
+    @Override
+    public Mono<World> getWorld(int id) {
+        return databaseClient.execute()
+                .sql("SELECT id, randomnumber FROM world WHERE id = $1")
+                .bind("$1", id)
+                .as(World.class)
+                .fetch()
+                .first();
+
+    }
+
+    public Mono<World> updateWorld(World world) {
+        return databaseClient.execute()
+                .sql("UPDATE world SET randomnumber=$2 WHERE id = $1")
+                .bind("$1", world.id)
+                .bind("$2", world.randomnumber)
+                .fetch()
+                .rowsUpdated()
+                .map(count -> world);
+    }
+
+    public Mono<World> findAndUpdateWorld(int id, int randomNumber) {
+        return getWorld(id).flatMap(world -> {
+            world.randomnumber = randomNumber;
+            return updateWorld(world);
+        });
+    }
+
+    @Override
+    public Flux<Fortune> fortunes() {
+        return databaseClient.execute()
+                .sql("SELECT id, message FROM fortune")
+                .as(Fortune.class)
+                .fetch()
+                .all();
+    }
+}

+ 60 - 0
frameworks/Java/spring-webflux/src/main/java/benchmark/repository/RxJdbcDbRepository.java

@@ -0,0 +1,60 @@
+package benchmark.repository;
+
+import benchmark.model.Fortune;
+import benchmark.model.World;
+import io.reactivex.Flowable;
+import org.davidmoten.rx.jdbc.Database;
+import org.springframework.context.annotation.Profile;
+import org.springframework.stereotype.Component;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+@Component
+@Profile("rxjdbc")
+public class RxJdbcDbRepository implements DbRepository {
+    private final Database db;
+
+    public RxJdbcDbRepository(Database db) {
+        this.db = db;
+    }
+
+    @Override
+    public Mono<World> getWorld(int id) {
+        String sql = "SELECT * FROM world WHERE id = ?";
+
+        Flowable<World> worldFlowable = db.select(sql)
+                .parameters(id)
+                .get(rs -> {
+                    World world = new World(rs.getInt("id"), rs.getInt("randomnumber"));
+                    return world;
+                });
+
+        return Mono.from(worldFlowable);
+    }
+
+    public Mono<World> updateWorld(World world) {
+        String sql = "UPDATE world SET randomnumber = ? WHERE id = ?";
+
+        Flowable<World> worldFlowable = db.update(sql)
+                .parameters(world.randomnumber, world.id)
+                .counts().map(cnt -> world);
+        return Mono.from(worldFlowable);
+    }
+
+    public Mono<World> findAndUpdateWorld(int id, int randomNumber) {
+        return getWorld(id).flatMap(world -> {
+            world.randomnumber = randomNumber;
+            return updateWorld(world);
+        });
+    }
+
+    @Override
+    public Flux<Fortune> fortunes() {
+        String sql = "SELECT * FROM fortune";
+
+        Flowable<Fortune> fortuneFlowable = db.select(sql)
+                .get(rs -> new Fortune(rs.getInt("id"), rs.getString("message")));
+
+        return Flux.from(fortuneFlowable);
+    }
+}

+ 104 - 0
frameworks/Java/spring-webflux/src/main/java/benchmark/web/WebfluxHandler.java

@@ -0,0 +1,104 @@
+package benchmark.web;
+
+import benchmark.model.Fortune;
+import benchmark.model.World;
+import benchmark.repository.DbRepository;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Component;
+import org.springframework.web.reactive.function.server.ServerRequest;
+import org.springframework.web.reactive.function.server.ServerResponse;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.*;
+import java.util.concurrent.ThreadLocalRandom;
+
+import static java.util.Comparator.comparing;
+
+@Component
+public class WebfluxHandler {
+    private final DbRepository dbRepository;
+
+    public WebfluxHandler(DbRepository dbRepository) {
+        this.dbRepository = dbRepository;
+    }
+
+    public Mono<ServerResponse> plaintext(ServerRequest request) {
+        return ServerResponse.ok()
+                .contentType(MediaType.TEXT_PLAIN)
+                .body(Mono.just("Hello, World!"), String.class);
+    }
+
+    public Mono<ServerResponse> json(ServerRequest request) {
+        return ServerResponse.ok()
+                .contentType(MediaType.APPLICATION_JSON)
+                .body(Mono.just(Map.of("message", "Hello, World!")), Map.class);
+    }
+
+    public Mono<ServerResponse> db(ServerRequest request) {
+        int id = randomWorldNumber();
+        Mono<World> world = dbRepository.getWorld(id)
+                .switchIfEmpty(Mono.error(new Exception("No World found with Id: " + id)));
+
+        return ServerResponse.ok()
+                .contentType(MediaType.APPLICATION_JSON)
+                .body(world, World.class);
+    }
+
+    public Mono<ServerResponse> queries(ServerRequest request) {
+        int queries = getQueries(request);
+
+        Mono<World>[] worlds = new Mono[queries];
+        Arrays.setAll(worlds, i -> dbRepository.getWorld(randomWorldNumber()));
+
+        return ServerResponse.ok()
+                .contentType(MediaType.APPLICATION_JSON)
+                .body(Flux.merge(worlds).collectList(), new ParameterizedTypeReference<List<World>>() {
+                });
+    }
+
+    private static int parseQueryCount(Optional<String> maybeTextValue) {
+        if (!maybeTextValue.isPresent()) {
+            return 1;
+        }
+        int parsedValue;
+        try {
+            parsedValue = Integer.parseInt(maybeTextValue.get());
+        } catch (NumberFormatException e) {
+            return 1;
+        }
+        return Math.min(500, Math.max(1, parsedValue));
+    }
+
+    public Mono<ServerResponse> updates(ServerRequest request) {
+        int count = getQueries(request);
+        Mono<World>[] worlds = new Mono[count];
+
+        Arrays.setAll(worlds, i -> dbRepository.findAndUpdateWorld(randomWorldNumber(), randomWorldNumber()));
+
+        return ServerResponse.ok()
+                .contentType(MediaType.APPLICATION_JSON)
+                .body(Flux.merge(worlds).collectList(), new ParameterizedTypeReference<List<World>>() {
+                });
+    }
+
+    public Mono<ServerResponse> fortunes(ServerRequest request) {
+        Mono<List<Fortune>> result = dbRepository.fortunes().collectList().flatMap(fortunes -> {
+            fortunes.add(new Fortune(0, "Additional fortune added at request time."));
+            fortunes.sort(comparing(fortune -> fortune.message));
+            return Mono.just(fortunes);
+        });
+
+        return ServerResponse.ok()
+                .render("fortunes", Collections.singletonMap("fortunes", result));
+    }
+
+    private static int getQueries(ServerRequest request) {
+        return parseQueryCount(request.queryParam("queries"));
+    }
+
+    private static int randomWorldNumber() {
+        return 1 + ThreadLocalRandom.current().nextInt(10000);
+    }
+}

+ 37 - 0
frameworks/Java/spring-webflux/src/main/java/benchmark/web/WebfluxRouter.java

@@ -0,0 +1,37 @@
+package benchmark.web;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.reactive.function.server.RouterFunction;
+import org.springframework.web.reactive.function.server.RouterFunctions;
+import org.springframework.web.reactive.function.server.ServerResponse;
+
+import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
+
+@Configuration
+public class WebfluxRouter {
+
+    @Bean
+    public RouterFunction<ServerResponse> route(WebfluxHandler handler) {
+        return RouterFunctions
+                .route(
+                        GET("/plaintext"),
+                        handler::plaintext)
+                .andRoute(
+                        GET("/json"),
+                        handler::json)
+                .andRoute(
+                        GET("/db"),
+                        handler::db)
+                .andRoute(
+                        GET("/queries"),
+                        handler::queries)
+                .andRoute(
+                        GET("/updates"),
+                        handler::updates)
+                .andRoute(
+                        GET("/fortunes"),
+                        handler::fortunes)
+                ;
+    }
+}

+ 21 - 9
frameworks/Java/spring-webflux/src/main/resources/application.yml

@@ -1,14 +1,10 @@
 ---
 spring:
-  profiles: jdbc
+  profiles: postgres
   datasource:
-    url: jdbc:postgresql://tfb-database:5432/hello_world
-    username: benchmarkdbuser
-    password: benchmarkdbpass
----
-
-spring:
-  profiles: pgclient
+    url: jdbc:postgresql://${database.host}:${database.port}/${database.name}
+    username: ${database.username}
+    password: ${database.password}
 
 database:
   name: hello_world
@@ -16,11 +12,27 @@ database:
   port: 5432
   username: benchmarkdbuser
   password: benchmarkdbpass
+
+---
+spring:
+  profiles: jdbc
+
 ---
+spring:
+  profiles: pgclient
+
+---
+spring:
+  profiles: rxjdbc
 
+---
+spring:
+  profiles: r2dbc
+
+---
 spring:
   profiles: mongo
 
 database:
   url: mongodb://tfb-database:27017
-  name: hello_world
+  name: hello_world