231419585
在這篇文章中,咱們將會看到怎樣在vert.x應用中使用HSQL,固然也可使用任意JDBC,以及使用vertx-jdbc-client提供的異步的API,這篇文章的代碼在github上。html
vert.x一個很重要的特色就是它的異步性。使用異步的API,不須要等結果返回,當有結果返回時,vert.x會主動通知。爲了說明這個,咱們來看一個簡單的例子。前端
咱們假設有個add
方法。通常來講,會像int r = add(1, 1)
這樣來使用它。這是一個同步的API,因此你必須等到返回結果。異步的API會是這樣:add(1, 1, r -> { /*do something with the result*/})
。在這個版本中,你傳入了一個Handler,當結果計算出來時才被調用。這個方法不返回任何東西,實現以下:java
public void add(int a, int b, Handler<Integer> resultHandler) { int r = a + b; resultHandler.handle(r); }
爲了不混淆概念,異步API並非多線程。像咱們在add例子裏看到的,並無涉及多線程。git
看了一些基本的異步的API,如今瞭解下vertx-jdbc-client
。這個組件可以讓咱們經過JDBC driver
與數據庫交互。這些交互都是異步的,之前這樣:github
String sql = "SELECT * FROM Products"; ResultSet rs = stmt.executeQuery(sql);
如今要這樣:web
connection.query("SELECT * FROM Products", result -> { // do something with the result });
這個模型更高效,當結果出來後vert.x通知,避免了等待結果。sql
在pom.xml
文件中增長兩個 Maven dependencies
數據庫
<dependency> <groupId>io.vertx</groupId> <artifactId>vertx-jdbc-client</artifactId> <version>3.1.0</version> </dependency> <dependency> <groupId>org.hsqldb</groupId> <artifactId>hsqldb</artifactId> <version>2.3.3</version> </dependency>
第一個依賴提供了vertx-jdbc-client
,第二個提供了HSQL JDBC
的驅動。若是你想使用另一個數據庫,修改這個依賴,同時你還須要修改JDBC url
和JDBC driver
名。json
建立JDBC 客戶端(client):多線程
在MyFirstVerticle
類中,聲明一個新變量JDBCClient jdbc
,而且在start
方法中添加:
jdbc = JDBCClient.createShared(vertx, config(), "My-Whisky-Collection");
建立了一個JDBC client實例,使用verticle的配置文件配置JDBC client。這個配置文件須要提供下面的配置才能讓JDBC client正常工做:
jdbc:hsqldb:mem:db?shutdown=true
org.hsqldb.jdbcDriver
有了client,接下來須要鏈接數據庫。鏈接數據庫是經過使用jdbc.getConnection
來實現的,jdbc.getConnection
須要傳入一個Handler<AsyncResult<SQLConnection>>
參數。咱們深刻的瞭解下這個類型。首先,這是一個Handler
,所以當結果準備好時它就會被調用。這個結果是AsyncResult<SQLConnection>
的一個實例。AsyncResult
是vert.x
提供的一個結構,使用它可以知道鏈接數據庫的操做是成功或失敗了。若是成功了,它就會提供一個結果,這裏結果是一個SQLConnection
的實例。
當你接收一個AsyncResult
的實例時,代碼一般是:
if (ar.failed()) { System.err.println("The operation has failed...: " + ar.cause().getMessage()); } else { // Use the result: result = ar.result(); }
須要獲取到SQLConnection
,而後啓動rest
的應用。由於變成了異步的,這須要改變啓動應用的方式。所以,若是將啓動序列劃分紅多塊:
startBackend( (connection) -> createSomeData(connection, (nothing) -> startWebApp( (http) -> completeStartup(http, fut) ), fut ), fut);
startBackend
- 獲取SQLConnection
對象,而後調用下一步createSomeData
- 初始化數據庫並插入數據。當完成後,調用下一步startWebApp
- 啓動web應用completeStartup
- 最後完成啓動fut
由vert.x傳入,通知已經啓動或者啓動過程當中遇到的問題。
startBackend
方法:
private void startBackend(Handler<AsyncResult<SQLConnection>> next, Future<Void> fut) { jdbc.getConnection(ar -> { if (ar.failed()) { fut.fail(ar.cause()); } else { next.handle(Future.succeededFuture(ar.result())); } }); }
這個方法獲取了一個SQLConnection對象,檢查操做是否完成。若是成功,會調用下一步。失敗了,就會報告一個錯誤。其餘的方法遵循一樣的模式:
客戶端已經準備好了,如今寫SQL。從createSomeData
方法開始,這個方法也是啓動順序中的一部分:
private void createSomeData(AsyncResult<SQLConnection> result, Handler<AsyncResult<Void>> next, Future<Void> fut) { if (result.failed()) { fut.fail(result.cause()); } else { SQLConnection connection = result.result(); connection.execute( "CREATE TABLE IF NOT EXISTS Whisky (id INTEGER IDENTITY, name varchar(100), " + "origin varchar(100))", ar -> { if (ar.failed()) { fut.fail(ar.cause()); connection.close(); return; } connection.query("SELECT * FROM Whisky", select -> { if (select.failed()) { fut.fail(ar.cause()); connection.close(); return; } if (select.result().getNumRows() == 0) { insert( new Whisky("Bowmore 15 Years Laimrig", "Scotland, Islay"), connection, (v) -> insert(new Whisky("Talisker 57° North", "Scotland, Island"), connection, (r) -> { next.handle(Future.<Void>succeededFuture()); connection.close(); })); } else { next.handle(Future.<Void>succeededFuture()); connection.close(); } }); }); } }
這個方法檢查SQLConnection
是否可用,而後執行一些SQL語句。首先,若是表不存在就建立表。看看下面代碼:
connection.execute( SQL statement, handler called when the statement has been executed )
handler
接收AsyncResult<Void>
,例如:只有是通知而已,沒有實際返回的結果。
操做完成後,別忘了關閉SQL連接。這個鏈接會被放入鏈接池而且能夠被重複利用。
在這個handler
的代碼裏,檢查了statement
是否正確的執行了,若是正確,咱們接下來檢查表是否含有數據,若是沒有,將會使用insert
方法插入數據:
private void insert(Whisky whisky, SQLConnection connection, Handler<AsyncResult<Whisky>> next) { String sql = "INSERT INTO Whisky (name, origin) VALUES ?, ?"; connection.updateWithParams(sql, new JsonArray().add(whisky.getName()).add(whisky.getOrigin()), (ar) -> { if (ar.failed()) { next.handle(Future.failedFuture(ar.cause())); return; } UpdateResult result = ar.result(); // Build a new whisky instance with the generated id. Whisky w = new Whisky(result.getKeys().getInteger(0), whisky.getName(), whisky.getOrigin()); next.handle(Future.succeededFuture(w)); }); }
這個方法使用帶有INSERT(插入)statement(聲明)的upateWithParams
方法,且傳入了值。這個方法避免了SQL
注入。一旦statement執行了(當數據庫沒有此條數據就會建立),就建立一個新的Whisky
對象,自動生成ID。
上面的方法都是啓動順序的一部分。可是,關於調用REST API的方法又是怎麼樣的呢?以getAll
方法爲例。這個方法被web應用前端調用,並檢索存儲的全部的產品:
private void getAll(RoutingContext routingContext) { jdbc.getConnection(ar -> { SQLConnection connection = ar.result(); connection.query("SELECT * FROM Whisky", result -> { List<Whisky> whiskies = result.result().getRows().stream().map(Whisky::new).collect(Collectors.toList()); routingContext.response() .putHeader("content-type", "application/json; charset=utf-8") .end(Json.encodePrettily(whiskies)); connection.close(); // Close the connection }); }); }
這個方法得到了一個SQLConnection
對象,而後發出一個查詢。一旦獲取到查詢結果,它會像以前的方法同樣寫HTTP response
。getOne
、deleteOne
、updateOne
和addOne
方法都是同樣的。注意,在response以後,須要要關閉SQL鏈接。
看下傳入到query方法的handler提供的結果。獲取了一個包含了查詢結果的ResultSet。每一行都是一個JsonObject,所以,若是你有一個數據對象使用JsonObject做爲惟一的參數,那麼建立這個對象很簡單。
須要小小的更新下測試程序,增長配置JDBCClient
。在MyFirstVerticleTest
類中,將setUp
方法中建立的DeploymentOption
對象修改爲:
DeploymentOptions options = new DeploymentOptions() .setConfig(new JsonObject() .put("http.port", port) .put("url", "jdbc:hsqldb:mem:test?shutdown=true") .put("driver_class", "org.hsqldb.jdbcDriver") );
除了http.port
,還配置了JDBC url
和JDBC
驅動。測試時,使用的是一個內存數據庫。在src/test/resources/my-it-config.json
文件中也要作一樣的修改。
{ "http.port": ${http.port}, "url": "jdbc:hsqldb:mem:it-test?shutdown=true", "driver_class": "org.hsqldb.jdbcDriver" }
src/main/conf/my-application-conf.json
文件也一樣須要修改,這不是爲了測試,而是爲了運行這個應用:
{ "http.port" : 8082, "url": "jdbc:hsqldb:file:db/whiskies", "driver_class": "org.hsqldb.jdbcDriver" }
這裏這個JDBC url
和上一個文件的有點不同,由於須要將數據庫存儲到硬盤中。
開始構建程序:
mvn clean package
沒有修改API(沒有更改發佈的java文件和REST接口),測試應該是能夠順利的運行的。
啓動應用:
java -jar target/my-first-app-1.0-SNAPSHOT-fat.jar -conf src/main/conf/my-application-conf.json
訪問http://localhost:8082/assets/index.html
,而後,你能夠看到這個應用使用的是數據庫了。這一次,就算重啓應用,這些數據仍然在,由於存儲產品被持久化到硬盤裏了。
這篇文章中,知道了怎麼在vert.x
裏使用JDBC
數據庫,並無不少複雜的東西。開始可能會被這個異步的開發模型驚訝到,可是,一旦你開始使用了,你就很難再回去了。
下一次,咱們將看到這個應用怎麼使用mongoDB來替換HSQL。
Stay tuned, and happy coding !