diff --git a/kotlin-examples/coroutines/README.md b/kotlin-examples/coroutines/README.md index fa43aa19a..b06ced57f 100644 --- a/kotlin-examples/coroutines/README.md +++ b/kotlin-examples/coroutines/README.md @@ -1,10 +1,17 @@ # Vert.x Kotlin Coroutines Example -A movie getRating REST application written in [Kotlin](https://kotlinlang.org/) to demonstrate how it can Kotlin -coroutines can be used with Vert.x. +A movie rating REST application written in [Kotlin](https://kotlinlang.org/) to demonstrate how Kotlin coroutines can be used with Vert.x. -Coroutines enables to write asynchronous code with sequential statements, the function `rateMovie` is the best example -showing two SQL client operations wrapped in a `use` block managing the SQL connection. +Kotlin Coroutines enables to write asynchronous code with sequential statements, the function `rateMovie` is the best example +showing two asynchronous SQL client operations could be executed sequentially just like writing synchronous code. + +## Setup the database + +Setup a MySQL database server with Docker + +``` +docker run -it --rm=true --name vertx-coroutine-example-test -e MYSQL_USER=vertx -e MYSQL_PASSWORD=vertx -e MYSQL_ROOT_PASSWORD=my-secret-pw -e MYSQL_DATABASE=vertx_example -p 3306:3306 mysql:8 +``` ## Running from the IDE @@ -19,10 +26,7 @@ Run the main function from the IDE ## Running from the CLI -Copy hsqldb jar to $VERTX_HOME/lib - ``` -> cp $HOME/.m2/repository/org/hsqldb/hsqldb/2.4.0/hsqldb-2.4.0.jar $VERTX_HOME/lib/ > vertx run src/main/kotlin/movierating/App.kt ``` @@ -37,14 +41,14 @@ You can know more about a movie {"id":"starwars","title":"Star Wars"} ``` -You can get the current getRating of a movie: +You can get the current rating of a movie: ``` > curl http://localhost:8080/getRating/indianajones {"id":"indianajones","getRating":5} ``` -Finally you can rateMovie a movie +Finally you can rate a movie ``` > curl -X POST http://localhost:8080/rateMovie/starwars?getRating=4 diff --git a/kotlin-examples/coroutines/pom.xml b/kotlin-examples/coroutines/pom.xml index 430588766..78ce01c8d 100644 --- a/kotlin-examples/coroutines/pom.xml +++ b/kotlin-examples/coroutines/pom.xml @@ -34,14 +34,9 @@ io.vertx - vertx-jdbc-client + vertx-mysql-client ${project.version} - - org.hsqldb - hsqldb - 2.3.4 - org.slf4j diff --git a/kotlin-examples/coroutines/src/main/kotlin/movierating/App.kt b/kotlin-examples/coroutines/src/main/kotlin/movierating/App.kt index b7d5f08fc..1963468b8 100644 --- a/kotlin-examples/coroutines/src/main/kotlin/movierating/App.kt +++ b/kotlin-examples/coroutines/src/main/kotlin/movierating/App.kt @@ -1,54 +1,54 @@ package movierating -import io.vertx.ext.jdbc.JDBCClient import io.vertx.ext.web.Route import io.vertx.ext.web.Router import io.vertx.ext.web.RoutingContext import io.vertx.kotlin.core.http.listenAwait -import io.vertx.kotlin.core.json.array -import io.vertx.kotlin.core.json.get import io.vertx.kotlin.core.json.json import io.vertx.kotlin.core.json.obj import io.vertx.kotlin.coroutines.CoroutineVerticle import io.vertx.kotlin.coroutines.dispatcher -import io.vertx.kotlin.ext.sql.executeAwait -import io.vertx.kotlin.ext.sql.getConnectionAwait -import io.vertx.kotlin.ext.sql.queryWithParamsAwait -import io.vertx.kotlin.ext.sql.updateWithParamsAwait +import io.vertx.kotlin.sqlclient.executeAwait +import io.vertx.mysqlclient.MySQLConnectOptions +import io.vertx.mysqlclient.MySQLPool +import io.vertx.sqlclient.* import kotlinx.coroutines.launch class App : CoroutineVerticle() { - private lateinit var client: JDBCClient + private lateinit var client: MySQLPool + private lateinit var getTitleByMovieId : PreparedQuery> + private lateinit var insertRatingWithMovieId : PreparedQuery> + private lateinit var getAverageRatingByMovieId : PreparedQuery> override suspend fun start() { - client = JDBCClient.createShared(vertx, json { - obj( - "url" to "jdbc:hsqldb:mem:test?shutdown=true", - "driver_class" to "org.hsqldb.jdbcDriver", - "max_pool_size-loop" to 30 - ) - }) + client = MySQLPool.pool(vertx, MySQLConnectOptions.fromUri("mysql://vertx:vertx@localhost:3306/vertx_example") + .setCachePreparedStatements(true) + .setPreparedStatementCacheMaxSize(1024), PoolOptions().setMaxSize(50)) // Populate database - val statements = listOf( - "CREATE TABLE MOVIE (ID VARCHAR(16) PRIMARY KEY, TITLE VARCHAR(256) NOT NULL)", - "CREATE TABLE RATING (ID INTEGER IDENTITY PRIMARY KEY, value INTEGER, MOVIE_ID VARCHAR(16))", - "INSERT INTO MOVIE (ID, TITLE) VALUES 'starwars', 'Star Wars'", - "INSERT INTO MOVIE (ID, TITLE) VALUES 'indianajones', 'Indiana Jones'", - "INSERT INTO RATING (VALUE, MOVIE_ID) VALUES 1, 'starwars'", - "INSERT INTO RATING (VALUE, MOVIE_ID) VALUES 5, 'starwars'", - "INSERT INTO RATING (VALUE, MOVIE_ID) VALUES 9, 'starwars'", - "INSERT INTO RATING (VALUE, MOVIE_ID) VALUES 10, 'starwars'", - "INSERT INTO RATING (VALUE, MOVIE_ID) VALUES 4, 'indianajones'", - "INSERT INTO RATING (VALUE, MOVIE_ID) VALUES 7, 'indianajones'", - "INSERT INTO RATING (VALUE, MOVIE_ID) VALUES 3, 'indianajones'", - "INSERT INTO RATING (VALUE, MOVIE_ID) VALUES 9, 'indianajones'" - ) - client.getConnectionAwait() - .use { connection -> statements.forEach { connection.executeAwait(it) } } + client.query(""" + DROP TABLE IF EXISTS movie; + DROP TABLE IF EXISTS rating; + CREATE TABLE movie (id VARCHAR(16) PRIMARY KEY, title VARCHAR(256) NOT NULL); + CREATE TABLE rating (id INTEGER AUTO_INCREMENT PRIMARY KEY, `value` INTEGER, movie_id VARCHAR(16)); + INSERT INTO movie (id, title) VALUES ('starwars', 'Star Wars'); + INSERT INTO movie (id, title) VALUES ('indianajones', 'Indiana Jones'); + INSERT INTO rating (`value`, movie_id) VALUES (1, 'starwars'); + INSERT INTO rating (`value`, movie_id) VALUES (5, 'starwars'); + INSERT INTO rating (`value`, movie_id) VALUES (9, 'starwars'); + INSERT INTO rating (`value`, movie_id) VALUES (10, 'starwars'); + INSERT INTO rating (`value`, movie_id) VALUES (4, 'indianajones'); + INSERT INTO rating (`value`, movie_id) VALUES (7, 'indianajones'); + INSERT INTO rating (`value`, movie_id) VALUES (3, 'indianajones'); + INSERT INTO rating (`value`, movie_id) VALUES (9, 'indianajones'); + """.trimIndent()).executeAwait() + + getTitleByMovieId = client.preparedQuery("SELECT title FROM movie WHERE id = ?") + insertRatingWithMovieId = client.preparedQuery("INSERT INTO rating (`value`, movie_id) VALUES (?, ?)") + getAverageRatingByMovieId = client.preparedQuery("SELECT AVG(`value`) AS avg_rating FROM rating WHERE movie_id = ? ") // Build Vert.x Web router val router = Router.router(vertx) @@ -64,38 +64,39 @@ class App : CoroutineVerticle() { // Send info about a movie suspend fun getMovie(ctx: RoutingContext) { - val id = ctx.pathParam("id") - val result = client.queryWithParamsAwait("SELECT TITLE FROM MOVIE WHERE ID=?", json { array(id) }) - if (result.rows.size == 1) { + val movieId = ctx.pathParam("id") + + val rowSet = getTitleByMovieId.executeAwait(Tuple.of(movieId)) + if (rowSet.size() == 1) { + val row = rowSet.iterator().next() ctx.response().end(json { - obj("id" to id, "title" to result.rows[0]["TITLE"]).encode() + obj("id" to movieId, "title" to row.getString("title")).encode() }) - } else { + } else{ ctx.response().setStatusCode(404).end() } } // Rate a movie suspend fun rateMovie(ctx: RoutingContext) { - val movie = ctx.pathParam("id") + val movieId = ctx.pathParam("id") val rating = Integer.parseInt(ctx.queryParam("getRating")[0]) - client.getConnectionAwait().use { connection -> - val result = connection.queryWithParamsAwait("SELECT TITLE FROM MOVIE WHERE ID=?", json { array(movie) }) - if (result.rows.size == 1) { - connection.updateWithParamsAwait("INSERT INTO RATING (VALUE, MOVIE_ID) VALUES ?, ?", json { array(rating, movie) }) - ctx.response().setStatusCode(200).end() - } else { - ctx.response().setStatusCode(404).end() - } + val titleRowSet = getTitleByMovieId.executeAwait(Tuple.of(movieId)) + if (titleRowSet.size() == 1) { + insertRatingWithMovieId.executeAwait(Tuple.of(rating, movieId)) + ctx.response().setStatusCode(200).end() + } else { + ctx.response().setStatusCode(404).end() } } // Get the current rating of a movie suspend fun getRating(ctx: RoutingContext) { - val id = ctx.pathParam("id") - val result = client.queryWithParamsAwait("SELECT AVG(VALUE) AS VALUE FROM RATING WHERE MOVIE_ID=?", json { array(id) }) + val movieId = ctx.pathParam("id") + val result = getAverageRatingByMovieId.executeAwait(Tuple.of(movieId)) + val averageRating = result.iterator().next().getInteger("avg_rating") ctx.response().end(json { - obj("id" to id, "getRating" to result.rows[0]["VALUE"]).encode() + obj("id" to movieId, "getRating" to averageRating).encode() }) }