├── .gitattributes ├── .github └── FUNDING.yml ├── .gitignore ├── .mvn └── wrapper │ └── maven-wrapper.properties ├── README.md ├── build-docker-images.sh ├── docker-compose.yml ├── documentation ├── jconsole-jpa-stream-naive.jpeg ├── jconsole-jpa-stream.jpeg ├── jconsole-r2dbc-stream.jpeg ├── project-diagram.excalidraw └── project-diagram.jpeg ├── dump-mysql-db.sh ├── init-kafka-topics.sh ├── init-mysql-db.sh ├── mvnw ├── mvnw.cmd ├── mysql ├── init-db.sql ├── load-100k-data.sql ├── load-1M-data.sql ├── load-200k-data.sql └── load-500k-data.sql ├── pom.xml ├── remove-docker-images.sh ├── streamer-data-jpa ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── ivanfranchin │ │ │ └── streamerdatajpa │ │ │ ├── StreamerDataJpaApplication.java │ │ │ ├── config │ │ │ └── ErrorAttributesConfig.java │ │ │ └── customer │ │ │ ├── Customer.java │ │ │ ├── CustomerController.java │ │ │ ├── CustomerEmitter.java │ │ │ ├── CustomerRepository.java │ │ │ └── RandomCustomerGenerator.java │ └── resources │ │ ├── application.properties │ │ └── banner.txt │ └── test │ └── java │ └── com │ └── ivanfranchin │ └── streamerdatajpa │ └── StreamerDataJpaApplicationTests.java └── streamer-data-r2dbc ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── ivanfranchin │ │ └── streamerdatar2dbc │ │ ├── StreamerDataR2dbcApplication.java │ │ ├── config │ │ └── ErrorAttributesConfig.java │ │ └── customer │ │ ├── Customer.java │ │ ├── CustomerController.java │ │ ├── CustomerEmitter.java │ │ ├── CustomerRepository.java │ │ └── RandomCustomerGenerator.java └── resources │ ├── application.properties │ ├── banner.txt │ └── db-schema.sql └── test └── java └── com └── ivanfranchin └── streamerdatar2dbc └── StreamerDataR2dbcApplicationTests.java /.gitattributes: -------------------------------------------------------------------------------- 1 | /mvnw text eol=lf 2 | *.cmd text eol=crlf 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: ivangfr 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### STS ### 7 | .apt_generated 8 | .classpath 9 | .factorypath 10 | .project 11 | .settings 12 | .springBeans 13 | .sts4-cache 14 | 15 | ### IntelliJ IDEA ### 16 | .idea 17 | *.iws 18 | *.iml 19 | *.ipr 20 | 21 | ### NetBeans ### 22 | /nbproject/private/ 23 | /nbbuild/ 24 | /dist/ 25 | /nbdist/ 26 | /.nb-gradle/ 27 | build/ 28 | !**/src/main/**/build/ 29 | !**/src/test/**/build/ 30 | 31 | ### VS Code ### 32 | .vscode/ 33 | 34 | ### MAC OS ### 35 | *.DS_Store 36 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | wrapperVersion=3.3.2 18 | distributionType=only-script 19 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # spring-data-jpa-r2dbc-mysql-stream-million-records 2 | 3 | In this project, we will implement two [`Spring Boot`](https://docs.spring.io/spring-boot/index.html) Java Web application called `streamer-data-jpa` and `streamer-data-r2dbc`. They both will fetch 1 million of customer records from [`MySQL`](https://www.mysql.com/) and stream them to [`Kafka`](https://kafka.apache.org/). The main goal is to compare the application's performance and resource utilization. 4 | 5 | ## Proof-of-Concepts & Articles 6 | 7 | On [ivangfr.github.io](https://ivangfr.github.io), I have compiled my Proof-of-Concepts (PoCs) and articles. You can easily search for the technology you are interested in by using the filter. Who knows, perhaps I have already implemented a PoC or written an article about what you are looking for. 8 | 9 | ## Project Diagram 10 | 11 | ![project-diagram](documentation/project-diagram.jpeg) 12 | 13 | ## Applications 14 | 15 | - ### streamer-data-jpa 16 | 17 | `Spring Boot` Web Java application that connects to `MySQL` using [`Spring Data JPA`](https://docs.spring.io/spring-data/jpa/reference/) and to `Kafka`. 18 | 19 | It provides some endpoints such as: 20 | - `PATCH api/customers/stream-naive[?limit=x]`: to stream customer records using a naive implementation with `Spring Data JPA`. 21 | - `PATCH api/customers/stream[?limit=x]`: to stream customer records using an improved implementation with `Java 8 Streams` and `Spring Data JPA` as explained in this [article](https://knes1.github.io/blog/2015/2015-10-19-streaming-mysql-results-using-java8-streams-and-spring-data.html). 22 | - `PATCH api/customers/load?amount=x`: to create a specific number of random customer records. 23 | 24 | - ### streamer-data-r2dbc 25 | 26 | `Spring Boot` Web Java application that connects to `MySQL` using [`Spring Data R2DBC`](https://docs.spring.io/spring-data/relational/reference/) and to `Kafka`. 27 | 28 | It provides some endpoints such as: 29 | - `PATCH api/customers/stream[?limit=x]`: to stream customer records. 30 | - `PATCH api/customers/load?amount=x`: to create a specific number of random customer records. 31 | 32 | ## Prerequisites 33 | 34 | - [`Java 21`](https://www.oracle.com/java/technologies/downloads/#java21) or higher. 35 | - A containerization tool (e.g., [`Docker`](https://www.docker.com), [`Podman`](https://podman.io), etc.) 36 | 37 | ## Start Environment 38 | 39 | - Open a terminal and inside the `spring-data-jpa-r2dbc-mysql-stream-million-records` root folder run: 40 | ```bash 41 | docker compose up -d 42 | ``` 43 | 44 | - Wait for Docker containers to be up and running. To check it, run: 45 | ```bash 46 | docker ps -a 47 | ``` 48 | 49 | - Once `MySQL`, `Kafka`, and `Zookeeper` are up and running, run the following scripts: 50 | 51 | - To create two `Kafka` topics: 52 | ```bash 53 | ./init-kafka-topics.sh 54 | ``` 55 | 56 | - To initialize the `MySQL` database and to create two `Kafka` topics: 57 | ```bash 58 | ./init-mysql-db.sh 1M 59 | ``` 60 | > **Note**: You can provide the following load amounts: 0, 100k, 200k, 500k, or 1M. 61 | 62 | ## Run applications with Maven 63 | 64 | Inside the `spring-data-jpa-r2dbc-mysql-stream-million-records`, run the following Maven commands in different terminals: 65 | 66 | - **streamer-data-jpa** 67 | ```bash 68 | ./mvnw clean spring-boot:run --projects streamer-data-jpa 69 | ``` 70 | 71 | - **streamer-data-r2dbc** 72 | ```bash 73 | ./mvnw clean spring-boot:run --projects streamer-data-r2dbc 74 | ``` 75 | 76 | ## Run applications as Docker containers 77 | 78 | - ### Build Docker Images 79 | 80 | - In a terminal, make sure you are in the `spring-data-jpa-r2dbc-mysql-stream-million-records` root folder. 81 | - Run the following script to build the Docker images: 82 | ```bash 83 | ./build-docker-images.sh 84 | ``` 85 | 86 | - ### Environment Variables 87 | 88 | - **streamer-data-jpa** 89 | 90 | | Environment Variable | Description | 91 | |----------------------|-------------------------------------------------------------------------| 92 | | `MYSQL_HOST` | Specify host of the `MySQL` database to use (default `localhost`) | 93 | | `MYSQL_PORT` | Specify port of the `MySQL` database to use (default `3306`) | 94 | | `KAFKA_HOST` | Specify host of the `Kafka` message broker to use (default `localhost`) | 95 | | `KAFKA_PORT` | Specify port of the `Kafka` message broker to use (default `29092`) | 96 | 97 | - **streamer-data-r2dbc** 98 | 99 | | Environment Variable | Description | 100 | |----------------------|-------------------------------------------------------------------------| 101 | | `MYSQL_HOST` | Specify host of the `MySQL` database to use (default `localhost`) | 102 | | `MYSQL_PORT` | Specify port of the `MySQL` database to use (default `3306`) | 103 | | `KAFKA_HOST` | Specify host of the `Kafka` message broker to use (default `localhost`) | 104 | | `KAFKA_PORT` | Specify port of the `Kafka` message broker to use (default `29092`) | 105 | 106 | - ### Start Docker Containers 107 | 108 | Run the following `docker run` commands in different terminals: 109 | 110 | - **streamer-data-jpa** 111 | ```bash 112 | docker run --rm --name streamer-data-jpa -p 9080:9080 \ 113 | -e MYSQL_HOST=mysql -e KAFKA_HOST=kafka -e KAFKA_PORT=9092 \ 114 | --network spring-data-jpa-r2dbc-mysql-stream-million-records_default \ 115 | ivanfranchin/streamer-data-jpa:1.0.0 116 | ``` 117 | 118 | - **streamer-data-r2dbc** 119 | ```bash 120 | docker run --rm --name streamer-data-r2dbc -p 9081:9081 \ 121 | -e MYSQL_HOST=mysql -e KAFKA_HOST=kafka -e KAFKA_PORT=9092 \ 122 | --network spring-data-jpa-r2dbc-mysql-stream-million-records_default \ 123 | ivanfranchin/streamer-data-r2dbc:1.0.0 124 | ``` 125 | 126 | ## Simulation with 1 million customer records 127 | 128 | Previously, during [Start Environment](#start-environment) step, we initialized MySQL with 1 million customer records. 129 | 130 | ### Resource Consumption Monitoring Tool 131 | 132 | - **Running applications with Maven** 133 | 134 | We will use [`JConsole`](https://openjdk.org/tools/svc/jconsole/) tool. To run it, open a new terminal and run: 135 | ```bash 136 | jconsole 137 | ``` 138 | 139 | - **Running applications as Docker containers** 140 | 141 | We will use the [`cAdvisor`](https://github.com/google/cadvisor) tool. In a browser, access: 142 | - To explore the running containers: http://localhost:8080/docker/ 143 | - To go directly to a specific container: 144 | - **streamer-data-jpa**: http://localhost:8080/docker/streamer-data-jpa 145 | - **streamer-data-r2dbc**: http://localhost:8080/docker/streamer-data-r2dbc 146 | 147 | ### Streaming customer records 148 | 149 | In another terminal, call the following `curl` commands to trigger the streaming of customer records from `MySQL` to `Kafka`. At the end of the `curl` command, the total time it took (in seconds) to process will be displayed. 150 | 151 | We can monitor the amount of messages and the messages themselves being streamed using [Kafdrop – Kafka Web UI](https://github.com/obsidiandynamics/kafdrop) at http://localhost:9000 152 | 153 | - **streamer-data-jpa** 154 | 155 | _Naive implementation_ 156 | ```bash 157 | curl -w "Response Time: %{time_total}s" -s -X PATCH localhost:9080/api/customers/stream-naive 158 | ``` 159 | 160 | _Better implementation_ 161 | ```bash 162 | curl -w "Response Time: %{time_total}s" -s -X PATCH localhost:9080/api/customers/stream 163 | ``` 164 | 165 | - **streamer-data-r2dbc** 166 | ```bash 167 | curl -w "Response Time: %{time_total}s" -s -X PATCH localhost:9081/api/customers/stream 168 | ``` 169 | 170 | ### Sample 171 | 172 | A simulation sample running the applications with Maven and using the `JConsole` tool: 173 | 174 | - **streamer-data-jpa** 175 | 176 | _Naive implementation_ 177 | ```text 178 | Response Time: 414.486126s 179 | ``` 180 | ![jconsole-jpa-stream-naive](documentation/jconsole-jpa-stream-naive.jpeg) 181 | 182 | _Better implementation_ 183 | ```text 184 | Response Time: 453.692525s 185 | ``` 186 | ![jconsole-jpa-stream](documentation/jconsole-jpa-stream.jpeg) 187 | 188 | - **streamer-data-r2dbc** 189 | ```text 190 | Response Time: 476.951654s 191 | ``` 192 | ![jconsole-r2dbc-stream](documentation/jconsole-r2dbc-stream.jpeg) 193 | 194 | ## Useful commands & links 195 | 196 | - **Kafdrop** 197 | 198 | `Kafdrop` can be accessed at http://localhost:9001 199 | 200 | - **MySQL monitor** 201 | 202 | To check data in the `customerdb` database: 203 | ```bash 204 | docker exec -it -e MYSQL_PWD=secret mysql mysql -uroot --database customerdb 205 | SELECT count(*) FROM customer; 206 | ``` 207 | 208 | To create a dump from the `customer` table in the `customerdb` database, make sure you are in the `spring-data-jpa-r2dbc-mysql-stream-million-records` root folder and run 209 | ```bash 210 | ./dump-mysql-db.sh 211 | ``` 212 | 213 | ## Shutdown 214 | 215 | - To stop `streamer-data-jpa` and `streamer-data-r2dbc`, go to the terminals where they are running and press `Ctrl+C`. 216 | - To stop and remove Docker Compose containers, network, and volumes, go to a terminal and, inside the `spring-data-jpa-r2dbc-mysql-stream-million-records` root folder, run the command below: 217 | ```bash 218 | docker compose down -v 219 | ``` 220 | 221 | ## Cleanup 222 | 223 | To remove all Docker images created by this project, go to a terminal and, inside the `spring-data-jpa-r2dbc-mysql-stream-million-records` root folder, run the following script: 224 | ```bash 225 | ./remove-docker-images.sh 226 | ``` 227 | -------------------------------------------------------------------------------- /build-docker-images.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | DOCKER_IMAGE_PREFIX="ivanfranchin" 4 | APP_VERSION="1.0.0" 5 | 6 | STREAMER_DATA_JPA_APP_NAME="streamer-data-jpa" 7 | STREAMER_DATA_R2DBC_APP_NAME="streamer-data-r2dbc" 8 | 9 | STREAMER_DATA_JPA_DOCKER_IMAGE_NAME="${DOCKER_IMAGE_PREFIX}/${STREAMER_DATA_JPA_APP_NAME}:${APP_VERSION}" 10 | STREAMER_DATA_R2DBC_DOCKER_IMAGE_NAME="${DOCKER_IMAGE_PREFIX}/${STREAMER_DATA_R2DBC_APP_NAME}:${APP_VERSION}" 11 | 12 | SKIP_TESTS="true" 13 | 14 | ./mvnw clean spring-boot:build-image \ 15 | --projects "$STREAMER_DATA_JPA_APP_NAME" \ 16 | -DskipTests="$SKIP_TESTS" \ 17 | -Dspring-boot.build-image.imageName="$STREAMER_DATA_JPA_DOCKER_IMAGE_NAME" 18 | 19 | ./mvnw clean spring-boot:build-image \ 20 | --projects "$STREAMER_DATA_R2DBC_APP_NAME" \ 21 | -DskipTests="$SKIP_TESTS" \ 22 | -Dspring-boot.build-image.imageName="$STREAMER_DATA_R2DBC_DOCKER_IMAGE_NAME" 23 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | 3 | mysql: 4 | image: 'mysql:9.2.0' 5 | container_name: 'mysql' 6 | ports: 7 | - '3306:3306' 8 | environment: 9 | - 'MYSQL_ROOT_PASSWORD=secret' 10 | - 'MYSQL_DATABASE=customerdb' 11 | healthcheck: 12 | test: 'mysqladmin ping -u root -p$${MYSQL_ROOT_PASSWORD}' 13 | 14 | zookeeper: 15 | image: 'confluentinc/cp-zookeeper:7.8.0' 16 | container_name: 'zookeeper' 17 | ports: 18 | - '2181:2181' 19 | environment: 20 | - 'ZOOKEEPER_CLIENT_PORT=2181' 21 | healthcheck: 22 | test: 'echo stat | nc localhost $$ZOOKEEPER_CLIENT_PORT' 23 | 24 | kafka: 25 | image: 'confluentinc/cp-kafka:7.8.0' 26 | container_name: 'kafka' 27 | depends_on: 28 | - 'zookeeper' 29 | ports: 30 | - '9092:9092' 31 | - '29092:29092' 32 | environment: 33 | - 'KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181' 34 | - 'KAFKA_LISTENER_SECURITY_PROTOCOL_MAP=PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT' 35 | - 'KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://kafka:9092,PLAINTEXT_HOST://localhost:29092' 36 | - 'KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1' 37 | healthcheck: 38 | test: [ "CMD", "nc", "-z", "localhost", "9092" ] 39 | 40 | kafdrop: 41 | image: 'obsidiandynamics/kafdrop:4.1.0' 42 | container_name: 'kafdrop' 43 | depends_on: 44 | - 'kafka' 45 | ports: 46 | - '9001:9000' 47 | environment: 48 | - 'KAFKA_BROKERCONNECT=kafka:9092' 49 | healthcheck: 50 | test: 'curl -f http://localhost:9000 || exit 1' 51 | 52 | cadvisor: 53 | image: 'gcr.io/cadvisor/cadvisor:v0.49.2' 54 | container_name: 'cadvisor' 55 | ports: 56 | - '8080:8080' 57 | volumes: 58 | - '/:/rootfs:ro' 59 | - '/var/run:/var/run:rw' 60 | - '/sys:/sys:ro' 61 | - '/var/lib/docker/:/var/lib/docker:ro' 62 | - '/var/run/docker.sock:/var/run/docker.sock' 63 | -------------------------------------------------------------------------------- /documentation/jconsole-jpa-stream-naive.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivangfr/spring-data-jpa-r2dbc-mysql-stream-million-records/235d687df95e76bb6824d3e5ce0d2909c4af4dcc/documentation/jconsole-jpa-stream-naive.jpeg -------------------------------------------------------------------------------- /documentation/jconsole-jpa-stream.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivangfr/spring-data-jpa-r2dbc-mysql-stream-million-records/235d687df95e76bb6824d3e5ce0d2909c4af4dcc/documentation/jconsole-jpa-stream.jpeg -------------------------------------------------------------------------------- /documentation/jconsole-r2dbc-stream.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivangfr/spring-data-jpa-r2dbc-mysql-stream-million-records/235d687df95e76bb6824d3e5ce0d2909c4af4dcc/documentation/jconsole-r2dbc-stream.jpeg -------------------------------------------------------------------------------- /documentation/project-diagram.excalidraw: -------------------------------------------------------------------------------- 1 | { 2 | "type": "excalidraw", 3 | "version": 2, 4 | "source": "https://excalidraw.com", 5 | "elements": [ 6 | { 7 | "type": "rectangle", 8 | "version": 1072, 9 | "versionNonce": 256750390, 10 | "isDeleted": false, 11 | "id": "NKmNZxYxWMCKh3prRiPwX", 12 | "fillStyle": "hachure", 13 | "strokeWidth": 1, 14 | "strokeStyle": "solid", 15 | "roughness": 1, 16 | "opacity": 100, 17 | "angle": 0, 18 | "x": 35.74492563848355, 19 | "y": 98.40989141541075, 20 | "strokeColor": "#000000", 21 | "backgroundColor": "#82c91e", 22 | "width": 209.18356323242188, 23 | "height": 99.67071533203125, 24 | "seed": 1526615674, 25 | "groupIds": [], 26 | "roundness": { 27 | "type": 3 28 | }, 29 | "boundElements": [ 30 | { 31 | "id": "mt7km_ERj00a2XOMSuQfO", 32 | "type": "arrow" 33 | }, 34 | { 35 | "id": "bG8RDFVh6zX-EIbktNsZA", 36 | "type": "arrow" 37 | } 38 | ], 39 | "updated": 1678958946730, 40 | "link": null, 41 | "locked": false 42 | }, 43 | { 44 | "type": "rectangle", 45 | "version": 1107, 46 | "versionNonce": 525807780, 47 | "isDeleted": false, 48 | "id": "AmDajC5Y1CXP4EO3n8tQr", 49 | "fillStyle": "hachure", 50 | "strokeWidth": 1, 51 | "strokeStyle": "solid", 52 | "roughness": 1, 53 | "opacity": 100, 54 | "angle": 0, 55 | "x": 325.93122329225093, 56 | "y": 47.66735296326232, 57 | "strokeColor": "#000000", 58 | "backgroundColor": "#15aabf", 59 | "width": 209.18356323242188, 60 | "height": 99.67071533203125, 61 | "seed": 1497471780, 62 | "groupIds": [], 63 | "roundness": { 64 | "type": 3 65 | }, 66 | "boundElements": [ 67 | { 68 | "type": "text", 69 | "id": "P5pUv584_fMEhcy1PvLgG" 70 | }, 71 | { 72 | "id": "mt7km_ERj00a2XOMSuQfO", 73 | "type": "arrow" 74 | }, 75 | { 76 | "id": "9SvHyzHCas23IjAWJtHKD", 77 | "type": "arrow" 78 | } 79 | ], 80 | "updated": 1678921064094, 81 | "link": null, 82 | "locked": false 83 | }, 84 | { 85 | "type": "text", 86 | "version": 1135, 87 | "versionNonce": 434036508, 88 | "isDeleted": false, 89 | "id": "P5pUv584_fMEhcy1PvLgG", 90 | "fillStyle": "hachure", 91 | "strokeWidth": 1, 92 | "strokeStyle": "solid", 93 | "roughness": 1, 94 | "opacity": 100, 95 | "angle": 0, 96 | "x": 363.70102920045406, 97 | "y": 63.90271062927794, 98 | "strokeColor": "#000000", 99 | "backgroundColor": "transparent", 100 | "width": 133.64395141601562, 101 | "height": 67.2, 102 | "seed": 160700700, 103 | "groupIds": [], 104 | "roundness": null, 105 | "boundElements": [], 106 | "updated": 1678921064094, 107 | "link": null, 108 | "locked": false, 109 | "fontSize": 28, 110 | "fontFamily": 1, 111 | "text": "streamer-\ndata-jpa", 112 | "textAlign": "center", 113 | "verticalAlign": "middle", 114 | "containerId": "AmDajC5Y1CXP4EO3n8tQr", 115 | "originalText": "streamer-\ndata-jpa" 116 | }, 117 | { 118 | "type": "rectangle", 119 | "version": 1181, 120 | "versionNonce": 99712036, 121 | "isDeleted": false, 122 | "id": "Ms1oYj4lUmfpt4ZJp8o_U", 123 | "fillStyle": "hachure", 124 | "strokeWidth": 1, 125 | "strokeStyle": "solid", 126 | "roughness": 1, 127 | "opacity": 100, 128 | "angle": 0, 129 | "x": 325.93122329225093, 130 | "y": 183.18468694763732, 131 | "strokeColor": "#000000", 132 | "backgroundColor": "#fd7e14", 133 | "width": 209.18356323242188, 134 | "height": 99.67071533203125, 135 | "seed": 1798893852, 136 | "groupIds": [], 137 | "roundness": { 138 | "type": 3 139 | }, 140 | "boundElements": [ 141 | { 142 | "type": "text", 143 | "id": "LT5WIKwQy0s1vrSimj1Kp" 144 | }, 145 | { 146 | "id": "bG8RDFVh6zX-EIbktNsZA", 147 | "type": "arrow" 148 | }, 149 | { 150 | "id": "I1985GrkFugJiEVsOTTrz", 151 | "type": "arrow" 152 | } 153 | ], 154 | "updated": 1678921064094, 155 | "link": null, 156 | "locked": false 157 | }, 158 | { 159 | "type": "text", 160 | "version": 1216, 161 | "versionNonce": 158112668, 162 | "isDeleted": false, 163 | "id": "LT5WIKwQy0s1vrSimj1Kp", 164 | "fillStyle": "hachure", 165 | "strokeWidth": 1, 166 | "strokeStyle": "solid", 167 | "roughness": 1, 168 | "opacity": 100, 169 | "angle": 0, 170 | "x": 352.1230339001611, 171 | "y": 199.42004461365295, 172 | "strokeColor": "#000000", 173 | "backgroundColor": "transparent", 174 | "width": 156.79994201660156, 175 | "height": 67.2, 176 | "seed": 903823012, 177 | "groupIds": [], 178 | "roundness": null, 179 | "boundElements": [], 180 | "updated": 1678921064094, 181 | "link": null, 182 | "locked": false, 183 | "fontSize": 28, 184 | "fontFamily": 1, 185 | "text": "streamer-\ndata-r2dbc", 186 | "textAlign": "center", 187 | "verticalAlign": "middle", 188 | "containerId": "Ms1oYj4lUmfpt4ZJp8o_U", 189 | "originalText": "streamer-\ndata-r2dbc" 190 | }, 191 | { 192 | "type": "rectangle", 193 | "version": 153, 194 | "versionNonce": 2126183964, 195 | "isDeleted": false, 196 | "id": "ounqTmIJa3dClSnWsBmsb", 197 | "fillStyle": "hachure", 198 | "strokeWidth": 1, 199 | "strokeStyle": "solid", 200 | "roughness": 1, 201 | "opacity": 100, 202 | "angle": 0, 203 | "x": 649.3680671643212, 204 | "y": 55.53902654724669, 205 | "strokeColor": "#000000", 206 | "backgroundColor": "#ced4da", 207 | "width": 556.10693359375, 208 | "height": 185.41244506835938, 209 | "seed": 826894236, 210 | "groupIds": [], 211 | "roundness": { 212 | "type": 3 213 | }, 214 | "boundElements": [], 215 | "updated": 1678921073970, 216 | "link": null, 217 | "locked": false 218 | }, 219 | { 220 | "type": "text", 221 | "version": 125, 222 | "versionNonce": 1020494492, 223 | "isDeleted": false, 224 | "id": "5DAHR9K0Fg4cUuO_OQPtC", 225 | "fillStyle": "hachure", 226 | "strokeWidth": 1, 227 | "strokeStyle": "solid", 228 | "roughness": 1, 229 | "opacity": 100, 230 | "angle": 0, 231 | "x": 674.6834968518212, 232 | "y": 73.03055486755919, 233 | "strokeColor": "#000000", 234 | "backgroundColor": "transparent", 235 | "width": 81.75994873046875, 236 | "height": 33.6, 237 | "seed": 446166940, 238 | "groupIds": [], 239 | "roundness": null, 240 | "boundElements": [], 241 | "updated": 1678920995164, 242 | "link": null, 243 | "locked": false, 244 | "fontSize": 28, 245 | "fontFamily": 1, 246 | "text": "Kafka", 247 | "textAlign": "left", 248 | "verticalAlign": "top", 249 | "containerId": null, 250 | "originalText": "Kafka" 251 | }, 252 | { 253 | "type": "rectangle", 254 | "version": 204, 255 | "versionNonce": 498763036, 256 | "isDeleted": false, 257 | "id": "CRITH3Yd4Hosy9P50xilS", 258 | "fillStyle": "hachure", 259 | "strokeWidth": 1, 260 | "strokeStyle": "solid", 261 | "roughness": 1, 262 | "opacity": 100, 263 | "angle": 0, 264 | "x": 664.1052497815087, 265 | "y": 121.75887518005919, 266 | "strokeColor": "#000000", 267 | "backgroundColor": "#868e96", 268 | "width": 530, 269 | "height": 37, 270 | "seed": 279223716, 271 | "groupIds": [], 272 | "roundness": { 273 | "type": 3 274 | }, 275 | "boundElements": [ 276 | { 277 | "type": "text", 278 | "id": "6Gv4FDgt62sBXmHF0Wsbn" 279 | }, 280 | { 281 | "id": "9SvHyzHCas23IjAWJtHKD", 282 | "type": "arrow" 283 | } 284 | ], 285 | "updated": 1678921017762, 286 | "link": null, 287 | "locked": false 288 | }, 289 | { 290 | "type": "text", 291 | "version": 99, 292 | "versionNonce": 1048329992, 293 | "isDeleted": false, 294 | "id": "6Gv4FDgt62sBXmHF0Wsbn", 295 | "fillStyle": "hachure", 296 | "strokeWidth": 1, 297 | "strokeStyle": "solid", 298 | "roughness": 1, 299 | "opacity": 100, 300 | "angle": 0, 301 | "x": 721.3154243420556, 302 | "y": 128.2588751800592, 303 | "strokeColor": "#000000", 304 | "backgroundColor": "transparent", 305 | "width": 415.57965087890625, 306 | "height": 24, 307 | "seed": 1737479844, 308 | "groupIds": [], 309 | "roundness": null, 310 | "boundElements": [], 311 | "updated": 1678960337937, 312 | "link": null, 313 | "locked": false, 314 | "fontSize": 20, 315 | "fontFamily": 1, 316 | "text": "com.ivanfranchin.streamerdatajpa.customer", 317 | "textAlign": "center", 318 | "verticalAlign": "middle", 319 | "containerId": "CRITH3Yd4Hosy9P50xilS", 320 | "originalText": "com.ivanfranchin.streamerdatajpa.customer" 321 | }, 322 | { 323 | "type": "rectangle", 324 | "version": 282, 325 | "versionNonce": 351333028, 326 | "isDeleted": false, 327 | "id": "v5AkS5FDc8CbOXdv7rhx5", 328 | "fillStyle": "hachure", 329 | "strokeWidth": 1, 330 | "strokeStyle": "solid", 331 | "roughness": 1, 332 | "opacity": 100, 333 | "angle": 0, 334 | "x": 665.1106208752587, 335 | "y": 175.9215949066217, 336 | "strokeColor": "#000000", 337 | "backgroundColor": "#868e96", 338 | "width": 530, 339 | "height": 37, 340 | "seed": 634340636, 341 | "groupIds": [], 342 | "roundness": { 343 | "type": 3 344 | }, 345 | "boundElements": [ 346 | { 347 | "type": "text", 348 | "id": "NuUdQTCuwu7Q9HVgucYMR" 349 | }, 350 | { 351 | "id": "I1985GrkFugJiEVsOTTrz", 352 | "type": "arrow" 353 | } 354 | ], 355 | "updated": 1678921017762, 356 | "link": null, 357 | "locked": false 358 | }, 359 | { 360 | "type": "text", 361 | "version": 177, 362 | "versionNonce": 1927259768, 363 | "isDeleted": false, 364 | "id": "NuUdQTCuwu7Q9HVgucYMR", 365 | "fillStyle": "hachure", 366 | "strokeWidth": 1, 367 | "strokeStyle": "solid", 368 | "roughness": 1, 369 | "opacity": 100, 370 | "angle": 0, 371 | "x": 709.9908393811181, 372 | "y": 182.4215949066217, 373 | "strokeColor": "#000000", 374 | "backgroundColor": "transparent", 375 | "width": 440.23956298828125, 376 | "height": 24, 377 | "seed": 1434987172, 378 | "groupIds": [], 379 | "roundness": null, 380 | "boundElements": [], 381 | "updated": 1678960340425, 382 | "link": null, 383 | "locked": false, 384 | "fontSize": 20, 385 | "fontFamily": 1, 386 | "text": "com.ivanfranchin.streamerdatar2dbc.customer", 387 | "textAlign": "center", 388 | "verticalAlign": "middle", 389 | "containerId": "v5AkS5FDc8CbOXdv7rhx5", 390 | "originalText": "com.ivanfranchin.streamerdatar2dbc.customer" 391 | }, 392 | { 393 | "type": "arrow", 394 | "version": 504, 395 | "versionNonce": 264353572, 396 | "isDeleted": false, 397 | "id": "mt7km_ERj00a2XOMSuQfO", 398 | "fillStyle": "hachure", 399 | "strokeWidth": 1, 400 | "strokeStyle": "solid", 401 | "roughness": 1, 402 | "opacity": 100, 403 | "angle": 0, 404 | "x": 251.53164138307125, 405 | "y": 148.00863705170514, 406 | "strokeColor": "#000000", 407 | "backgroundColor": "transparent", 408 | "width": 73.99383544921875, 409 | "height": 55.2036041222314, 410 | "seed": 1307233052, 411 | "groupIds": [], 412 | "roundness": { 413 | "type": 2 414 | }, 415 | "boundElements": [], 416 | "updated": 1678921052096, 417 | "link": null, 418 | "locked": false, 419 | "startBinding": { 420 | "elementId": "NKmNZxYxWMCKh3prRiPwX", 421 | "focus": 0.6096305567466442, 422 | "gap": 6.6031525121658206 423 | }, 424 | "endBinding": { 425 | "elementId": "AmDajC5Y1CXP4EO3n8tQr", 426 | "focus": 0.6581908872686082, 427 | "gap": 1.9189605712890625 428 | }, 429 | "lastCommittedPoint": null, 430 | "startArrowhead": null, 431 | "endArrowhead": null, 432 | "points": [ 433 | [ 434 | 0, 435 | 0 436 | ], 437 | [ 438 | 73.99383544921875, 439 | -55.2036041222314 440 | ] 441 | ] 442 | }, 443 | { 444 | "type": "arrow", 445 | "version": 446, 446 | "versionNonce": 108808604, 447 | "isDeleted": false, 448 | "id": "bG8RDFVh6zX-EIbktNsZA", 449 | "fillStyle": "hachure", 450 | "strokeWidth": 1, 451 | "strokeStyle": "solid", 452 | "roughness": 1, 453 | "opacity": 100, 454 | "angle": 0, 455 | "x": 250.36727370729, 456 | "y": 172.0254659093897, 457 | "strokeColor": "#000000", 458 | "backgroundColor": "transparent", 459 | "width": 73.05073547363281, 460 | "height": 57.286986091019514, 461 | "seed": 638632356, 462 | "groupIds": [], 463 | "roundness": { 464 | "type": 2 465 | }, 466 | "boundElements": [], 467 | "updated": 1678921054043, 468 | "link": null, 469 | "locked": false, 470 | "startBinding": { 471 | "elementId": "NKmNZxYxWMCKh3prRiPwX", 472 | "focus": -0.5118734307250778, 473 | "gap": 5.4387848363845706 474 | }, 475 | "endBinding": { 476 | "elementId": "Ms1oYj4lUmfpt4ZJp8o_U", 477 | "focus": -0.5998793716083615, 478 | "gap": 1 479 | }, 480 | "lastCommittedPoint": null, 481 | "startArrowhead": null, 482 | "endArrowhead": null, 483 | "points": [ 484 | [ 485 | 0, 486 | 0 487 | ], 488 | [ 489 | 73.05073547363281, 490 | 57.286986091019514 491 | ] 492 | ] 493 | }, 494 | { 495 | "type": "arrow", 496 | "version": 171, 497 | "versionNonce": 1145516708, 498 | "isDeleted": false, 499 | "id": "9SvHyzHCas23IjAWJtHKD", 500 | "fillStyle": "hachure", 501 | "strokeWidth": 1, 502 | "strokeStyle": "solid", 503 | "roughness": 1, 504 | "opacity": 100, 505 | "angle": 0, 506 | "x": 544.7629646252587, 507 | "y": 89.17795681798617, 508 | "strokeColor": "#000000", 509 | "backgroundColor": "transparent", 510 | "width": 118.34228515625, 511 | "height": 50.44253679712642, 512 | "seed": 472181916, 513 | "groupIds": [], 514 | "roundness": { 515 | "type": 2 516 | }, 517 | "boundElements": [], 518 | "updated": 1678921052097, 519 | "link": null, 520 | "locked": false, 521 | "startBinding": { 522 | "elementId": "AmDajC5Y1CXP4EO3n8tQr", 523 | "focus": -0.5968922608144533, 524 | "gap": 8.134963989257812 525 | }, 526 | "endBinding": { 527 | "elementId": "CRITH3Yd4Hosy9P50xilS", 528 | "focus": -0.8576529172545128, 529 | "gap": 1 530 | }, 531 | "lastCommittedPoint": null, 532 | "startArrowhead": null, 533 | "endArrowhead": "arrow", 534 | "points": [ 535 | [ 536 | 0, 537 | 0 538 | ], 539 | [ 540 | 118.34228515625, 541 | 50.44253679712642 542 | ] 543 | ] 544 | }, 545 | { 546 | "type": "arrow", 547 | "version": 198, 548 | "versionNonce": 66631196, 549 | "isDeleted": false, 550 | "id": "I1985GrkFugJiEVsOTTrz", 551 | "fillStyle": "hachure", 552 | "strokeWidth": 1, 553 | "strokeStyle": "solid", 554 | "roughness": 1, 555 | "opacity": 100, 556 | "angle": 0, 557 | "x": 547.0083259533837, 558 | "y": 244.94312430354216, 559 | "strokeColor": "#000000", 560 | "backgroundColor": "transparent", 561 | "width": 114.87799072265625, 562 | "height": 52.95842953141633, 563 | "seed": 795323036, 564 | "groupIds": [], 565 | "roundness": { 566 | "type": 2 567 | }, 568 | "boundElements": [], 569 | "updated": 1678921054043, 570 | "link": null, 571 | "locked": false, 572 | "startBinding": { 573 | "elementId": "Ms1oYj4lUmfpt4ZJp8o_U", 574 | "focus": 0.6762736578361374, 575 | "gap": 13.406753540039062 576 | }, 577 | "endBinding": { 578 | "elementId": "v5AkS5FDc8CbOXdv7rhx5", 579 | "focus": 0.896372358254555, 580 | "gap": 3.22430419921875 581 | }, 582 | "lastCommittedPoint": null, 583 | "startArrowhead": null, 584 | "endArrowhead": "arrow", 585 | "points": [ 586 | [ 587 | 0, 588 | 0 589 | ], 590 | [ 591 | 114.87799072265625, 592 | -52.95842953141633 593 | ] 594 | ] 595 | }, 596 | { 597 | "type": "text", 598 | "version": 9, 599 | "versionNonce": 667212714, 600 | "isDeleted": false, 601 | "id": "bVIKmPnSniwli4yMWH3S9", 602 | "fillStyle": "hachure", 603 | "strokeWidth": 1, 604 | "strokeStyle": "solid", 605 | "roughness": 1, 606 | "opacity": 100, 607 | "angle": 0, 608 | "x": 55.102808375258746, 609 | "y": 108.55241155701232, 610 | "strokeColor": "#000000", 611 | "backgroundColor": "#82c91e", 612 | "width": 88.11595153808594, 613 | "height": 33.6, 614 | "seed": 1452769450, 615 | "groupIds": [], 616 | "roundness": null, 617 | "boundElements": [], 618 | "updated": 1678958962020, 619 | "link": null, 620 | "locked": false, 621 | "fontSize": 28, 622 | "fontFamily": 1, 623 | "text": "MySQL", 624 | "textAlign": "left", 625 | "verticalAlign": "top", 626 | "containerId": null, 627 | "originalText": "MySQL" 628 | }, 629 | { 630 | "type": "rectangle", 631 | "version": 123, 632 | "versionNonce": 310960310, 633 | "isDeleted": false, 634 | "id": "4fc1vt0vFO5Mygi9yw4OM", 635 | "fillStyle": "hachure", 636 | "strokeWidth": 1, 637 | "strokeStyle": "solid", 638 | "roughness": 1, 639 | "opacity": 100, 640 | "angle": 0, 641 | "x": 57.12938918580562, 642 | "y": 153.37041692810607, 643 | "strokeColor": "#000000", 644 | "backgroundColor": "#82c91e", 645 | "width": 166, 646 | "height": 34, 647 | "seed": 1790957162, 648 | "groupIds": [], 649 | "roundness": { 650 | "type": 3 651 | }, 652 | "boundElements": [ 653 | { 654 | "type": "text", 655 | "id": "uoa9nuHJ4APwhgmLDV7ea" 656 | } 657 | ], 658 | "updated": 1678959697241, 659 | "link": null, 660 | "locked": false 661 | }, 662 | { 663 | "type": "text", 664 | "version": 42, 665 | "versionNonce": 1854188342, 666 | "isDeleted": false, 667 | "id": "uoa9nuHJ4APwhgmLDV7ea", 668 | "fillStyle": "hachure", 669 | "strokeWidth": 1, 670 | "strokeStyle": "solid", 671 | "roughness": 1, 672 | "opacity": 100, 673 | "angle": 0, 674 | "x": 96.7094367932275, 675 | "y": 158.37041692810607, 676 | "strokeColor": "#000000", 677 | "backgroundColor": "#82c91e", 678 | "width": 86.83990478515625, 679 | "height": 24, 680 | "seed": 414325046, 681 | "groupIds": [], 682 | "roundness": null, 683 | "boundElements": [], 684 | "updated": 1678959699787, 685 | "link": null, 686 | "locked": false, 687 | "fontSize": 20, 688 | "fontFamily": 1, 689 | "text": "customer", 690 | "textAlign": "center", 691 | "verticalAlign": "middle", 692 | "containerId": "4fc1vt0vFO5Mygi9yw4OM", 693 | "originalText": "customer" 694 | } 695 | ], 696 | "appState": { 697 | "gridSize": null, 698 | "viewBackgroundColor": "#ffffff" 699 | }, 700 | "files": {} 701 | } -------------------------------------------------------------------------------- /documentation/project-diagram.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivangfr/spring-data-jpa-r2dbc-mysql-stream-million-records/235d687df95e76bb6824d3e5ce0d2909c4af4dcc/documentation/project-diagram.jpeg -------------------------------------------------------------------------------- /dump-mysql-db.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | docker exec -e MYSQL_PWD=secret mysql mysqldump -t -uroot customerdb customer > mysql/load-data.sql 4 | -------------------------------------------------------------------------------- /init-kafka-topics.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | NUM_PARTITIONS=5 4 | docker exec zookeeper kafka-topics --create --bootstrap-server kafka:9092 --replication-factor 1 --partitions $NUM_PARTITIONS --topic com.ivanfranchin.streamerdatajpa.customer 5 | docker exec zookeeper kafka-topics --create --bootstrap-server kafka:9092 --replication-factor 1 --partitions $NUM_PARTITIONS --topic com.ivanfranchin.streamerdatar2dbc.customer 6 | -------------------------------------------------------------------------------- /init-mysql-db.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | MYSQL_PWD=secret 4 | docker exec -i -e MYSQL_PWD=$MYSQL_PWD mysql mysql -uroot customerdb < mysql/init-db.sql 5 | 6 | if [ "$1" != "0" ] && 7 | [ "$1" != "100k" ] && 8 | [ "$1" != "200k" ] && 9 | [ "$1" != "500k" ] && 10 | [ "$1" != "1M" ]; 11 | then 12 | printf "Invalid load data amount provided!" 13 | printf "\nValid Parameters: 0, 100k, 200k, 500k or 1M\n" 14 | exit 1 15 | fi 16 | 17 | if [ "$1" = "100k" ] || 18 | [ "$1" = "200k" ] || 19 | [ "$1" = "500k" ] || 20 | [ "$1" = "1M" ]; 21 | then 22 | docker exec -i -e MYSQL_PWD=$MYSQL_PWD mysql mysql -uroot customerdb < mysql/load-$1-data.sql 23 | fi 24 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Apache Maven Wrapper startup batch script, version 3.3.2 23 | # 24 | # Optional ENV vars 25 | # ----------------- 26 | # JAVA_HOME - location of a JDK home dir, required when download maven via java source 27 | # MVNW_REPOURL - repo url base for downloading maven distribution 28 | # MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven 29 | # MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output 30 | # ---------------------------------------------------------------------------- 31 | 32 | set -euf 33 | [ "${MVNW_VERBOSE-}" != debug ] || set -x 34 | 35 | # OS specific support. 36 | native_path() { printf %s\\n "$1"; } 37 | case "$(uname)" in 38 | CYGWIN* | MINGW*) 39 | [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" 40 | native_path() { cygpath --path --windows "$1"; } 41 | ;; 42 | esac 43 | 44 | # set JAVACMD and JAVACCMD 45 | set_java_home() { 46 | # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched 47 | if [ -n "${JAVA_HOME-}" ]; then 48 | if [ -x "$JAVA_HOME/jre/sh/java" ]; then 49 | # IBM's JDK on AIX uses strange locations for the executables 50 | JAVACMD="$JAVA_HOME/jre/sh/java" 51 | JAVACCMD="$JAVA_HOME/jre/sh/javac" 52 | else 53 | JAVACMD="$JAVA_HOME/bin/java" 54 | JAVACCMD="$JAVA_HOME/bin/javac" 55 | 56 | if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then 57 | echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 58 | echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 59 | return 1 60 | fi 61 | fi 62 | else 63 | JAVACMD="$( 64 | 'set' +e 65 | 'unset' -f command 2>/dev/null 66 | 'command' -v java 67 | )" || : 68 | JAVACCMD="$( 69 | 'set' +e 70 | 'unset' -f command 2>/dev/null 71 | 'command' -v javac 72 | )" || : 73 | 74 | if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then 75 | echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 76 | return 1 77 | fi 78 | fi 79 | } 80 | 81 | # hash string like Java String::hashCode 82 | hash_string() { 83 | str="${1:-}" h=0 84 | while [ -n "$str" ]; do 85 | char="${str%"${str#?}"}" 86 | h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) 87 | str="${str#?}" 88 | done 89 | printf %x\\n $h 90 | } 91 | 92 | verbose() { :; } 93 | [ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } 94 | 95 | die() { 96 | printf %s\\n "$1" >&2 97 | exit 1 98 | } 99 | 100 | trim() { 101 | # MWRAPPER-139: 102 | # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. 103 | # Needed for removing poorly interpreted newline sequences when running in more 104 | # exotic environments such as mingw bash on Windows. 105 | printf "%s" "${1}" | tr -d '[:space:]' 106 | } 107 | 108 | # parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties 109 | while IFS="=" read -r key value; do 110 | case "${key-}" in 111 | distributionUrl) distributionUrl=$(trim "${value-}") ;; 112 | distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; 113 | esac 114 | done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" 115 | [ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" 116 | 117 | case "${distributionUrl##*/}" in 118 | maven-mvnd-*bin.*) 119 | MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ 120 | case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in 121 | *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; 122 | :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; 123 | :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; 124 | :Linux*x86_64*) distributionPlatform=linux-amd64 ;; 125 | *) 126 | echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 127 | distributionPlatform=linux-amd64 128 | ;; 129 | esac 130 | distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" 131 | ;; 132 | maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; 133 | *) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; 134 | esac 135 | 136 | # apply MVNW_REPOURL and calculate MAVEN_HOME 137 | # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ 138 | [ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" 139 | distributionUrlName="${distributionUrl##*/}" 140 | distributionUrlNameMain="${distributionUrlName%.*}" 141 | distributionUrlNameMain="${distributionUrlNameMain%-bin}" 142 | MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" 143 | MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" 144 | 145 | exec_maven() { 146 | unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : 147 | exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" 148 | } 149 | 150 | if [ -d "$MAVEN_HOME" ]; then 151 | verbose "found existing MAVEN_HOME at $MAVEN_HOME" 152 | exec_maven "$@" 153 | fi 154 | 155 | case "${distributionUrl-}" in 156 | *?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; 157 | *) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; 158 | esac 159 | 160 | # prepare tmp dir 161 | if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then 162 | clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } 163 | trap clean HUP INT TERM EXIT 164 | else 165 | die "cannot create temp dir" 166 | fi 167 | 168 | mkdir -p -- "${MAVEN_HOME%/*}" 169 | 170 | # Download and Install Apache Maven 171 | verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." 172 | verbose "Downloading from: $distributionUrl" 173 | verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" 174 | 175 | # select .zip or .tar.gz 176 | if ! command -v unzip >/dev/null; then 177 | distributionUrl="${distributionUrl%.zip}.tar.gz" 178 | distributionUrlName="${distributionUrl##*/}" 179 | fi 180 | 181 | # verbose opt 182 | __MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' 183 | [ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v 184 | 185 | # normalize http auth 186 | case "${MVNW_PASSWORD:+has-password}" in 187 | '') MVNW_USERNAME='' MVNW_PASSWORD='' ;; 188 | has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; 189 | esac 190 | 191 | if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then 192 | verbose "Found wget ... using wget" 193 | wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" 194 | elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then 195 | verbose "Found curl ... using curl" 196 | curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" 197 | elif set_java_home; then 198 | verbose "Falling back to use Java to download" 199 | javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" 200 | targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" 201 | cat >"$javaSource" <<-END 202 | public class Downloader extends java.net.Authenticator 203 | { 204 | protected java.net.PasswordAuthentication getPasswordAuthentication() 205 | { 206 | return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); 207 | } 208 | public static void main( String[] args ) throws Exception 209 | { 210 | setDefault( new Downloader() ); 211 | java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); 212 | } 213 | } 214 | END 215 | # For Cygwin/MinGW, switch paths to Windows format before running javac and java 216 | verbose " - Compiling Downloader.java ..." 217 | "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" 218 | verbose " - Running Downloader.java ..." 219 | "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" 220 | fi 221 | 222 | # If specified, validate the SHA-256 sum of the Maven distribution zip file 223 | if [ -n "${distributionSha256Sum-}" ]; then 224 | distributionSha256Result=false 225 | if [ "$MVN_CMD" = mvnd.sh ]; then 226 | echo "Checksum validation is not supported for maven-mvnd." >&2 227 | echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 228 | exit 1 229 | elif command -v sha256sum >/dev/null; then 230 | if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then 231 | distributionSha256Result=true 232 | fi 233 | elif command -v shasum >/dev/null; then 234 | if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then 235 | distributionSha256Result=true 236 | fi 237 | else 238 | echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 239 | echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 240 | exit 1 241 | fi 242 | if [ $distributionSha256Result = false ]; then 243 | echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 244 | echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 245 | exit 1 246 | fi 247 | fi 248 | 249 | # unzip and move 250 | if command -v unzip >/dev/null; then 251 | unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" 252 | else 253 | tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" 254 | fi 255 | printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" 256 | mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" 257 | 258 | clean || : 259 | exec_maven "$@" 260 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | <# : batch portion 2 | @REM ---------------------------------------------------------------------------- 3 | @REM Licensed to the Apache Software Foundation (ASF) under one 4 | @REM or more contributor license agreements. See the NOTICE file 5 | @REM distributed with this work for additional information 6 | @REM regarding copyright ownership. The ASF licenses this file 7 | @REM to you under the Apache License, Version 2.0 (the 8 | @REM "License"); you may not use this file except in compliance 9 | @REM with the License. You may obtain a copy of the License at 10 | @REM 11 | @REM http://www.apache.org/licenses/LICENSE-2.0 12 | @REM 13 | @REM Unless required by applicable law or agreed to in writing, 14 | @REM software distributed under the License is distributed on an 15 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | @REM KIND, either express or implied. See the License for the 17 | @REM specific language governing permissions and limitations 18 | @REM under the License. 19 | @REM ---------------------------------------------------------------------------- 20 | 21 | @REM ---------------------------------------------------------------------------- 22 | @REM Apache Maven Wrapper startup batch script, version 3.3.2 23 | @REM 24 | @REM Optional ENV vars 25 | @REM MVNW_REPOURL - repo url base for downloading maven distribution 26 | @REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven 27 | @REM MVNW_VERBOSE - true: enable verbose log; others: silence the output 28 | @REM ---------------------------------------------------------------------------- 29 | 30 | @IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) 31 | @SET __MVNW_CMD__= 32 | @SET __MVNW_ERROR__= 33 | @SET __MVNW_PSMODULEP_SAVE=%PSModulePath% 34 | @SET PSModulePath= 35 | @FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( 36 | IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) 37 | ) 38 | @SET PSModulePath=%__MVNW_PSMODULEP_SAVE% 39 | @SET __MVNW_PSMODULEP_SAVE= 40 | @SET __MVNW_ARG0_NAME__= 41 | @SET MVNW_USERNAME= 42 | @SET MVNW_PASSWORD= 43 | @IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) 44 | @echo Cannot start maven from wrapper >&2 && exit /b 1 45 | @GOTO :EOF 46 | : end batch / begin powershell #> 47 | 48 | $ErrorActionPreference = "Stop" 49 | if ($env:MVNW_VERBOSE -eq "true") { 50 | $VerbosePreference = "Continue" 51 | } 52 | 53 | # calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties 54 | $distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl 55 | if (!$distributionUrl) { 56 | Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" 57 | } 58 | 59 | switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { 60 | "maven-mvnd-*" { 61 | $USE_MVND = $true 62 | $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" 63 | $MVN_CMD = "mvnd.cmd" 64 | break 65 | } 66 | default { 67 | $USE_MVND = $false 68 | $MVN_CMD = $script -replace '^mvnw','mvn' 69 | break 70 | } 71 | } 72 | 73 | # apply MVNW_REPOURL and calculate MAVEN_HOME 74 | # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ 75 | if ($env:MVNW_REPOURL) { 76 | $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } 77 | $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" 78 | } 79 | $distributionUrlName = $distributionUrl -replace '^.*/','' 80 | $distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' 81 | $MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" 82 | if ($env:MAVEN_USER_HOME) { 83 | $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" 84 | } 85 | $MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' 86 | $MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" 87 | 88 | if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { 89 | Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" 90 | Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" 91 | exit $? 92 | } 93 | 94 | if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { 95 | Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" 96 | } 97 | 98 | # prepare tmp dir 99 | $TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile 100 | $TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" 101 | $TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null 102 | trap { 103 | if ($TMP_DOWNLOAD_DIR.Exists) { 104 | try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } 105 | catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } 106 | } 107 | } 108 | 109 | New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null 110 | 111 | # Download and Install Apache Maven 112 | Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." 113 | Write-Verbose "Downloading from: $distributionUrl" 114 | Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" 115 | 116 | $webclient = New-Object System.Net.WebClient 117 | if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { 118 | $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) 119 | } 120 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 121 | $webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null 122 | 123 | # If specified, validate the SHA-256 sum of the Maven distribution zip file 124 | $distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum 125 | if ($distributionSha256Sum) { 126 | if ($USE_MVND) { 127 | Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." 128 | } 129 | Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash 130 | if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { 131 | Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." 132 | } 133 | } 134 | 135 | # unzip and move 136 | Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null 137 | Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null 138 | try { 139 | Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null 140 | } catch { 141 | if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { 142 | Write-Error "fail to move MAVEN_HOME" 143 | } 144 | } finally { 145 | try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } 146 | catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } 147 | } 148 | 149 | Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" 150 | -------------------------------------------------------------------------------- /mysql/init-db.sql: -------------------------------------------------------------------------------- 1 | USE customerdb; 2 | 3 | DROP TABLE IF EXISTS customer; 4 | 5 | CREATE TABLE customer ( 6 | id bigint NOT NULL AUTO_INCREMENT, 7 | first_name varchar(255) NOT NULL, 8 | last_name varchar(255) NOT NULL, 9 | PRIMARY KEY (id) 10 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 11 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.5.0 9 | 10 | 11 | com.ivanfranchin 12 | spring-data-jpa-r2dbc-mysql-stream-million-records 13 | 1.0.0 14 | pom 15 | spring-data-jpa-r2dbc-mysql-stream-million-records 16 | Demo project for Spring Boot 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 21 32 | 33 | 34 | streamer-data-jpa 35 | streamer-data-r2dbc 36 | 37 | 38 | -------------------------------------------------------------------------------- /remove-docker-images.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | docker rmi ivanfranchin/streamer-data-jpa:1.0.0 4 | docker rmi ivanfranchin/streamer-data-r2dbc:1.0.0 5 | -------------------------------------------------------------------------------- /streamer-data-jpa/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.ivanfranchin 7 | spring-data-jpa-r2dbc-mysql-stream-million-records 8 | 1.0.0 9 | ../pom.xml 10 | 11 | streamer-data-jpa 12 | streamer-data-jpa 13 | Demo project for Spring Boot 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 2025.0.0 29 | 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-starter-data-jpa 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-starter-web 38 | 39 | 40 | org.springframework.cloud 41 | spring-cloud-stream 42 | 43 | 44 | org.springframework.cloud 45 | spring-cloud-stream-binder-kafka 46 | 47 | 48 | org.springframework.kafka 49 | spring-kafka 50 | 51 | 52 | 53 | com.mysql 54 | mysql-connector-j 55 | runtime 56 | 57 | 58 | org.projectlombok 59 | lombok 60 | true 61 | 62 | 63 | org.springframework.boot 64 | spring-boot-starter-test 65 | test 66 | 67 | 68 | org.springframework.cloud 69 | spring-cloud-stream-test-binder 70 | test 71 | 72 | 73 | org.springframework.kafka 74 | spring-kafka-test 75 | test 76 | 77 | 78 | 79 | 80 | 81 | org.springframework.cloud 82 | spring-cloud-dependencies 83 | ${spring-cloud.version} 84 | pom 85 | import 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | org.apache.maven.plugins 94 | maven-compiler-plugin 95 | 96 | 97 | 98 | org.projectlombok 99 | lombok 100 | 101 | 102 | 103 | 104 | 105 | org.springframework.boot 106 | spring-boot-maven-plugin 107 | 108 | 109 | 110 | org.projectlombok 111 | lombok 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /streamer-data-jpa/src/main/java/com/ivanfranchin/streamerdatajpa/StreamerDataJpaApplication.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.streamerdatajpa; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class StreamerDataJpaApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(StreamerDataJpaApplication.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /streamer-data-jpa/src/main/java/com/ivanfranchin/streamerdatajpa/config/ErrorAttributesConfig.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.streamerdatajpa.config; 2 | 3 | import org.springframework.boot.web.error.ErrorAttributeOptions; 4 | import org.springframework.boot.web.error.ErrorAttributeOptions.Include; 5 | import org.springframework.boot.web.servlet.error.DefaultErrorAttributes; 6 | import org.springframework.boot.web.servlet.error.ErrorAttributes; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.web.context.request.WebRequest; 10 | 11 | import java.util.Map; 12 | 13 | @Configuration 14 | public class ErrorAttributesConfig { 15 | 16 | @Bean 17 | ErrorAttributes errorAttributes() { 18 | return new DefaultErrorAttributes() { 19 | @Override 20 | public Map getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) { 21 | return super.getErrorAttributes(webRequest, 22 | options.including(Include.EXCEPTION, Include.MESSAGE, Include.BINDING_ERRORS)); 23 | } 24 | }; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /streamer-data-jpa/src/main/java/com/ivanfranchin/streamerdatajpa/customer/Customer.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.streamerdatajpa.customer; 2 | 3 | import jakarta.persistence.Entity; 4 | import jakarta.persistence.GeneratedValue; 5 | import jakarta.persistence.GenerationType; 6 | import jakarta.persistence.Id; 7 | import lombok.Data; 8 | 9 | @Data 10 | @Entity 11 | public class Customer { 12 | 13 | @Id 14 | @GeneratedValue(strategy = GenerationType.IDENTITY) 15 | private Long id; 16 | 17 | private String firstName; 18 | private String lastName; 19 | } 20 | -------------------------------------------------------------------------------- /streamer-data-jpa/src/main/java/com/ivanfranchin/streamerdatajpa/customer/CustomerController.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.streamerdatajpa.customer; 2 | 3 | import jakarta.persistence.EntityManager; 4 | import lombok.RequiredArgsConstructor; 5 | import org.springframework.transaction.annotation.Transactional; 6 | import org.springframework.web.bind.annotation.PatchMapping; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RequestParam; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | import java.util.stream.Stream; 12 | 13 | @RequiredArgsConstructor 14 | @RestController 15 | @RequestMapping("/api/customers") 16 | public class CustomerController { 17 | 18 | private final CustomerRepository customerRepository; 19 | private final CustomerEmitter customerStream; 20 | private final RandomCustomerGenerator randomCustomerGenerator; 21 | private final EntityManager entityManager; 22 | 23 | @PatchMapping("/load") 24 | public void loadCustomers(@RequestParam Integer amount) { 25 | customerRepository.saveAll(randomCustomerGenerator.generate(amount)); 26 | } 27 | 28 | @PatchMapping("/stream-naive") 29 | public void streamNaiveCustomers() { 30 | customerRepository.findAll().forEach(customerStream::send); 31 | } 32 | 33 | @Transactional(readOnly = true) 34 | @PatchMapping("/stream") 35 | public void streamCustomers() { 36 | try (Stream customers = customerRepository.streamAll()) { 37 | customers.forEach(customer -> { 38 | customerStream.send(customer); 39 | entityManager.detach(customer); 40 | }); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /streamer-data-jpa/src/main/java/com/ivanfranchin/streamerdatajpa/customer/CustomerEmitter.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.streamerdatajpa.customer; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.cloud.stream.function.StreamBridge; 6 | import org.springframework.messaging.Message; 7 | import org.springframework.messaging.support.MessageBuilder; 8 | import org.springframework.stereotype.Component; 9 | 10 | @Slf4j 11 | @RequiredArgsConstructor 12 | @Component 13 | public class CustomerEmitter { 14 | 15 | private final StreamBridge streamBridge; 16 | 17 | public void send(Customer customer) { 18 | Message message = MessageBuilder.withPayload(customer) 19 | .setHeader("partitionKey", customer.getId()) 20 | .build(); 21 | streamBridge.send("customers-out-0", message); 22 | log.info("Headers: {}; Payload: {}", message.getHeaders(), message.getPayload()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /streamer-data-jpa/src/main/java/com/ivanfranchin/streamerdatajpa/customer/CustomerRepository.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.streamerdatajpa.customer; 2 | 3 | import jakarta.persistence.QueryHint; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.data.jpa.repository.Query; 6 | import org.springframework.data.jpa.repository.QueryHints; 7 | import org.springframework.stereotype.Repository; 8 | 9 | import java.util.stream.Stream; 10 | 11 | import static org.hibernate.jpa.HibernateHints.HINT_CACHEABLE; 12 | import static org.hibernate.jpa.HibernateHints.HINT_FETCH_SIZE; 13 | import static org.hibernate.jpa.HibernateHints.HINT_READ_ONLY; 14 | 15 | @Repository 16 | public interface CustomerRepository extends JpaRepository { 17 | 18 | @QueryHints(value = { 19 | @QueryHint(name = HINT_FETCH_SIZE, value = "" + Integer.MIN_VALUE), 20 | @QueryHint(name = HINT_CACHEABLE, value = "false"), 21 | @QueryHint(name = HINT_READ_ONLY, value = "true") 22 | }) 23 | @Query(value = "SELECT c FROM Customer c") 24 | Stream streamAll(); 25 | } 26 | -------------------------------------------------------------------------------- /streamer-data-jpa/src/main/java/com/ivanfranchin/streamerdatajpa/customer/RandomCustomerGenerator.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.streamerdatajpa.customer; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import java.security.SecureRandom; 6 | import java.util.List; 7 | import java.util.Random; 8 | import java.util.stream.Collectors; 9 | import java.util.stream.IntStream; 10 | 11 | @Component 12 | public class RandomCustomerGenerator { 13 | 14 | private final Random random = new SecureRandom(); 15 | 16 | private static final List FIRST_NAMES = List.of( 17 | "Liam", "Noah", "Oliver", "Elijah", "William", "James", "Benjamin", "Lucas", "Henry", "Alexander", "Mason", 18 | "Michael", "Ethan", "Daniel", "Jacob", "Logan", "Jackson", "Levi", "Sebastian", "Mateo", "Jack", "Owen", 19 | "Theodore", "Aiden", "Samuel", "Joseph", "John", "David", "Wyatt", "Matthew", "Luke", "Asher", "Carter", 20 | "Julian", "Grayson", "Leo", "Jayden", "Gabriel", "Isaac", "Lincoln", "Anthony", "Hudson", "Dylan", "Ezra", 21 | "Thomas", "Charles", "Christopher", "Jaxon", "Maverick", "Josiah" 22 | ); 23 | private static final List LAST_NAMES = List.of( 24 | "Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis", "Rodriguez", "Martinez", 25 | "Hernandez", "Lopez", "Gonzalez", "Wilson", "Anderson", "Thomas", "Taylor", "Moore", "Jackson", "Martin", 26 | "Lee", "Perez", "Thompson", "White", "Harris", "Sanchez", "Clark", "Ramirez", "Lewis", "Robinson", 27 | "Walker", "Young", "Allen", "King", "Wright", "Scott", "Torres", "Nguyen", "Hill", "Flores", "Green", 28 | "Adams", "Nelson", "Baker", "Hall", "Rivera", "Campbell", "Mitchell", "Carter", "Roberts" 29 | ); 30 | 31 | public List generate(Integer amount) { 32 | return IntStream.rangeClosed(1, amount) 33 | .mapToObj(i -> { 34 | Customer customer = new Customer(); 35 | customer.setFirstName(FIRST_NAMES.get(random.nextInt(FIRST_NAMES.size()))); 36 | customer.setLastName(LAST_NAMES.get(random.nextInt(LAST_NAMES.size()))); 37 | return customer; 38 | }).collect(Collectors.toList()); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /streamer-data-jpa/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=9080 2 | spring.application.name=streamer-data-jpa 3 | 4 | spring.datasource.url=jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/customerdb?characterEncoding=UTF-8&serverTimezone=UTC 5 | spring.datasource.username=root 6 | spring.datasource.password=secret 7 | 8 | spring.cloud.stream.kafka.binder.brokers=${KAFKA_HOST:localhost}:${KAFKA_PORT:29092} 9 | spring.cloud.stream.bindings.customers-out-0.destination=com.ivanfranchin.streamerdatajpa.customer 10 | spring.cloud.stream.bindings.customers-out-0.content-type=application/json 11 | spring.cloud.stream.bindings.customers-out-0.producer.partition-key-expression=headers['partitionKey'] 12 | 13 | logging.level.org.hibernate.SQL=DEBUG 14 | -------------------------------------------------------------------------------- /streamer-data-jpa/src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | _ _ _ _ 2 | ___| |_ _ __ ___ __ _ _ __ ___ ___ _ __ __| | __ _| |_ __ _ (_)_ __ __ _ 3 | / __| __| '__/ _ \/ _` | '_ ` _ \ / _ \ '__|____ / _` |/ _` | __/ _` |_____| | '_ \ / _` | 4 | \__ \ |_| | | __/ (_| | | | | | | __/ | |_____| (_| | (_| | || (_| |_____| | |_) | (_| | 5 | |___/\__|_| \___|\__,_|_| |_| |_|\___|_| \__,_|\__,_|\__\__,_| _/ | .__/ \__,_| 6 | |__/|_| 7 | :: Spring Boot :: ${spring-boot.formatted-version} 8 | -------------------------------------------------------------------------------- /streamer-data-jpa/src/test/java/com/ivanfranchin/streamerdatajpa/StreamerDataJpaApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.streamerdatajpa; 2 | 3 | import org.junit.jupiter.api.Disabled; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | 7 | @Disabled 8 | @SpringBootTest 9 | class StreamerDataJpaApplicationTests { 10 | 11 | @Test 12 | void contextLoads() { 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /streamer-data-r2dbc/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.ivanfranchin 7 | spring-data-jpa-r2dbc-mysql-stream-million-records 8 | 1.0.0 9 | ../pom.xml 10 | 11 | streamer-data-r2dbc 12 | streamer-data-r2dbc 13 | Demo project for Spring Boot 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 2025.0.0 29 | 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-starter-data-r2dbc 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-starter-webflux 38 | 39 | 40 | org.springframework.cloud 41 | spring-cloud-stream 42 | 43 | 44 | org.springframework.cloud 45 | spring-cloud-stream-binder-kafka 46 | 47 | 48 | org.springframework.kafka 49 | spring-kafka 50 | 51 | 52 | 53 | com.mysql 54 | mysql-connector-j 55 | runtime 56 | 57 | 58 | io.asyncer 59 | r2dbc-mysql 60 | runtime 61 | 62 | 63 | org.projectlombok 64 | lombok 65 | true 66 | 67 | 68 | org.springframework.boot 69 | spring-boot-starter-test 70 | test 71 | 72 | 73 | io.projectreactor 74 | reactor-test 75 | test 76 | 77 | 78 | org.springframework.cloud 79 | spring-cloud-stream-test-binder 80 | test 81 | 82 | 83 | org.springframework.kafka 84 | spring-kafka-test 85 | test 86 | 87 | 88 | 89 | 90 | 91 | org.springframework.cloud 92 | spring-cloud-dependencies 93 | ${spring-cloud.version} 94 | pom 95 | import 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | org.apache.maven.plugins 104 | maven-compiler-plugin 105 | 106 | 107 | 108 | org.projectlombok 109 | lombok 110 | 111 | 112 | 113 | 114 | 115 | org.springframework.boot 116 | spring-boot-maven-plugin 117 | 118 | 119 | 120 | org.projectlombok 121 | lombok 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /streamer-data-r2dbc/src/main/java/com/ivanfranchin/streamerdatar2dbc/StreamerDataR2dbcApplication.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.streamerdatar2dbc; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class StreamerDataR2dbcApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(StreamerDataR2dbcApplication.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /streamer-data-r2dbc/src/main/java/com/ivanfranchin/streamerdatar2dbc/config/ErrorAttributesConfig.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.streamerdatar2dbc.config; 2 | 3 | import org.springframework.boot.web.error.ErrorAttributeOptions; 4 | import org.springframework.boot.web.error.ErrorAttributeOptions.Include; 5 | import org.springframework.boot.web.reactive.error.DefaultErrorAttributes; 6 | import org.springframework.boot.web.reactive.error.ErrorAttributes; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.web.reactive.function.server.ServerRequest; 10 | 11 | import java.util.Map; 12 | 13 | @Configuration 14 | public class ErrorAttributesConfig { 15 | 16 | @Bean 17 | ErrorAttributes errorAttributes() { 18 | return new DefaultErrorAttributes() { 19 | @Override 20 | public Map getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) { 21 | return super.getErrorAttributes(request, 22 | options.including(Include.EXCEPTION, Include.MESSAGE, Include.BINDING_ERRORS)); 23 | } 24 | }; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /streamer-data-r2dbc/src/main/java/com/ivanfranchin/streamerdatar2dbc/customer/Customer.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.streamerdatar2dbc.customer; 2 | 3 | import lombok.Data; 4 | import org.springframework.data.annotation.Id; 5 | 6 | @Data 7 | public class Customer { 8 | 9 | @Id 10 | private Long id; 11 | 12 | private String firstName; 13 | private String lastName; 14 | } 15 | -------------------------------------------------------------------------------- /streamer-data-r2dbc/src/main/java/com/ivanfranchin/streamerdatar2dbc/customer/CustomerController.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.streamerdatar2dbc.customer; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.web.bind.annotation.PatchMapping; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | import org.springframework.web.bind.annotation.RequestParam; 7 | import org.springframework.web.bind.annotation.RestController; 8 | import reactor.core.publisher.Mono; 9 | 10 | @RequiredArgsConstructor 11 | @RestController 12 | @RequestMapping("/api/customers") 13 | public class CustomerController { 14 | 15 | private final CustomerRepository customerRepository; 16 | private final CustomerEmitter customerStream; 17 | private final RandomCustomerGenerator randomCustomerGenerator; 18 | 19 | @PatchMapping("/load") 20 | public Mono loadCustomers(@RequestParam Integer amount) { 21 | return customerRepository.saveAll(randomCustomerGenerator.generate(amount)).then(); 22 | } 23 | 24 | @PatchMapping("/stream") 25 | public Mono streamCustomers() { 26 | return customerRepository.findAll().doOnNext(customerStream::send).then(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /streamer-data-r2dbc/src/main/java/com/ivanfranchin/streamerdatar2dbc/customer/CustomerEmitter.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.streamerdatar2dbc.customer; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.cloud.stream.function.StreamBridge; 6 | import org.springframework.messaging.Message; 7 | import org.springframework.messaging.support.MessageBuilder; 8 | import org.springframework.stereotype.Component; 9 | 10 | @Slf4j 11 | @RequiredArgsConstructor 12 | @Component 13 | public class CustomerEmitter { 14 | 15 | private final StreamBridge streamBridge; 16 | 17 | public void send(Customer customer) { 18 | Message message = MessageBuilder.withPayload(customer) 19 | .setHeader("partitionKey", customer.getId()) 20 | .build(); 21 | streamBridge.send("customers-out-0", message); 22 | log.info("Headers: {}; Payload: {}", message.getHeaders(), message.getPayload()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /streamer-data-r2dbc/src/main/java/com/ivanfranchin/streamerdatar2dbc/customer/CustomerRepository.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.streamerdatar2dbc.customer; 2 | 3 | import org.springframework.data.r2dbc.repository.R2dbcRepository; 4 | import org.springframework.stereotype.Repository; 5 | 6 | @Repository 7 | public interface CustomerRepository extends R2dbcRepository { 8 | } 9 | -------------------------------------------------------------------------------- /streamer-data-r2dbc/src/main/java/com/ivanfranchin/streamerdatar2dbc/customer/RandomCustomerGenerator.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.streamerdatar2dbc.customer; 2 | 3 | import org.springframework.stereotype.Component; 4 | import reactor.core.publisher.Flux; 5 | 6 | import java.security.SecureRandom; 7 | import java.util.List; 8 | import java.util.Random; 9 | import java.util.stream.IntStream; 10 | 11 | @Component 12 | public class RandomCustomerGenerator { 13 | 14 | private final Random random = new SecureRandom(); 15 | 16 | private static final List FIRST_NAMES = List.of( 17 | "Liam", "Noah", "Oliver", "Elijah", "William", "James", "Benjamin", "Lucas", "Henry", "Alexander", "Mason", 18 | "Michael", "Ethan", "Daniel", "Jacob", "Logan", "Jackson", "Levi", "Sebastian", "Mateo", "Jack", "Owen", 19 | "Theodore", "Aiden", "Samuel", "Joseph", "John", "David", "Wyatt", "Matthew", "Luke", "Asher", "Carter", 20 | "Julian", "Grayson", "Leo", "Jayden", "Gabriel", "Isaac", "Lincoln", "Anthony", "Hudson", "Dylan", "Ezra", 21 | "Thomas", "Charles", "Christopher", "Jaxon", "Maverick", "Josiah" 22 | ); 23 | private static final List LAST_NAMES = List.of( 24 | "Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis", "Rodriguez", "Martinez", 25 | "Hernandez", "Lopez", "Gonzalez", "Wilson", "Anderson", "Thomas", "Taylor", "Moore", "Jackson", "Martin", 26 | "Lee", "Perez", "Thompson", "White", "Harris", "Sanchez", "Clark", "Ramirez", "Lewis", "Robinson", 27 | "Walker", "Young", "Allen", "King", "Wright", "Scott", "Torres", "Nguyen", "Hill", "Flores", "Green", 28 | "Adams", "Nelson", "Baker", "Hall", "Rivera", "Campbell", "Mitchell", "Carter", "Roberts" 29 | ); 30 | 31 | public Flux generate(Integer amount) { 32 | return Flux.fromStream(IntStream.rangeClosed(1, amount) 33 | .mapToObj(i -> { 34 | Customer customer = new Customer(); 35 | customer.setFirstName(FIRST_NAMES.get(random.nextInt(FIRST_NAMES.size()))); 36 | customer.setLastName(LAST_NAMES.get(random.nextInt(LAST_NAMES.size()))); 37 | return customer; 38 | })); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /streamer-data-r2dbc/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=9081 2 | spring.application.name=streamer-data-r2dbc 3 | 4 | spring.r2dbc.url=r2dbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306} 5 | spring.r2dbc.username=root 6 | spring.r2dbc.password=secret 7 | spring.r2dbc.name=customerdb 8 | 9 | spring.cloud.stream.kafka.binder.brokers=${KAFKA_HOST:localhost}:${KAFKA_PORT:29092} 10 | spring.cloud.stream.bindings.customers-out-0.destination=com.ivanfranchin.streamerdatar2dbc.customer 11 | spring.cloud.stream.bindings.customers-out-0.content-type=application/json 12 | spring.cloud.stream.bindings.customers-out-0.producer.partition-key-expression=headers['partitionKey'] 13 | 14 | logging.level.org.springframework.r2dbc=DEBUG 15 | -------------------------------------------------------------------------------- /streamer-data-r2dbc/src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | _ _ _ ____ _ _ 2 | ___| |_ _ __ ___ __ _ _ __ ___ ___ _ __ __| | __ _| |_ __ _ _ _|___ \ __| | |__ ___ 3 | / __| __| '__/ _ \/ _` | '_ ` _ \ / _ \ '__|____ / _` |/ _` | __/ _` |_____| '__|__) / _` | '_ \ / __| 4 | \__ \ |_| | | __/ (_| | | | | | | __/ | |_____| (_| | (_| | || (_| |_____| | / __/ (_| | |_) | (__ 5 | |___/\__|_| \___|\__,_|_| |_| |_|\___|_| \__,_|\__,_|\__\__,_| |_| |_____\__,_|_.__/ \___| 6 | :: Spring Boot :: ${spring-boot.formatted-version} 7 | -------------------------------------------------------------------------------- /streamer-data-r2dbc/src/main/resources/db-schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE customer ( 2 | id bigint NOT NULL AUTO_INCREMENT, 3 | first_name varchar(255) NOT NULL, 4 | last_name varchar(255) NOT NULL, 5 | PRIMARY KEY (id) 6 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -------------------------------------------------------------------------------- /streamer-data-r2dbc/src/test/java/com/ivanfranchin/streamerdatar2dbc/StreamerDataR2dbcApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.streamerdatar2dbc; 2 | 3 | import org.junit.jupiter.api.Disabled; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | 7 | @Disabled 8 | @SpringBootTest 9 | class StreamerDataR2dbcApplicationTests { 10 | 11 | @Test 12 | void contextLoads() { 13 | } 14 | } 15 | --------------------------------------------------------------------------------