1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        Vert.x 操作數(shù)據(jù)庫

        共 6200字,需瀏覽 13分鐘

         ·

        2021-03-26 00:32

        31d105d25d4bb70e8eeaa3086bf50b9f.webp哈嘍,大家好,歡迎閱讀閑話Java的Vert.x部分,本篇是閑話Vert.x的第四期,《Vert.x操作數(shù)據(jù)庫》。本期由閑話哥帶您了解,如何通過Vert.x來連接數(shù)據(jù)庫。

        后續(xù)所使用的代碼均開源在:https://github.com/happy-fly/wxcode


        為什么不用Mybatis或者Hibernate

        Vert.x提供異步訪問數(shù)據(jù)庫的API,可能這里有朋友會(huì)有疑惑,直接使用我們之前的熟悉的Mybatis或者Hibernate不行嗎,可行,但數(shù)據(jù)庫操作是一個(gè)耗時(shí)操作,使用傳統(tǒng)的同步模型,容易阻塞線程,導(dǎo)致整體性能下降,因此我們對(duì)于數(shù)據(jù)庫操作,需要使用Vert.x提供的異步API。

        Vert.x提供的API層級(jí)非常低,可以說是僅僅在原生JDBC基礎(chǔ)上封裝了一層異步接口。所有的對(duì)數(shù)據(jù)庫操作都需要通過編寫SQL來完成,參數(shù)的封裝和結(jié)果的獲取都需要手動(dòng)的來實(shí)現(xiàn),對(duì)于習(xí)慣使用ORM框架的開發(fā)者可能會(huì)非常的不習(xí)慣。

        下面就來一個(gè)具體的例子來對(duì)數(shù)據(jù)庫進(jìn)行個(gè)crud。


        增刪改查

        Vert.x的數(shù)據(jù)庫操作還是比較簡單的,類似于Apache的DbUtils或者是Spring的JDBCTemplate?;旧暇褪菍懥薙QL,然后填上參數(shù),再讀取結(jié)果。

        我個(gè)人認(rèn)為,在Vert.x中是不提倡使用PO等對(duì)象的,數(shù)據(jù)封裝用JsonObject,那么對(duì)于數(shù)據(jù)的序列化、反序列化包括數(shù)據(jù)的傳輸,異構(gòu)平臺(tái)的數(shù)據(jù)交互就都滿足了。所以在我參與的系統(tǒng)中,都沒有涉及到類似于User,Dept之類的對(duì)象,數(shù)據(jù)就存儲(chǔ)到JsonObject中。

        在開發(fā)中,你需要轉(zhuǎn)變思想,不能總想著封裝對(duì)象。比如在下面的例子中,查詢數(shù)據(jù)庫的結(jié)果,官方的API就幫助我們把結(jié)果封裝到JsonObject中了,對(duì)于多行多列的結(jié)果,就會(huì)封裝為List<JsonObject>。

        廢話不多說,我們實(shí)現(xiàn)一個(gè)基于User信息的增刪改查。

        準(zhǔn)備工作

        引入依賴包,這里需要引入vertx-jdbc和mysql驅(qū)動(dòng)包

        <dependency>    <groupId>io.vertx</groupId>    <artifactId>vertx-jdbc-client</artifactId>    <version>${vertx.version}</version></dependency><dependency>    <groupId>mysql</groupId>    <artifactId>mysql-connector-java</artifactId>    <version>8.0.13</version></dependency>

        主方法

        創(chuàng)建一個(gè)Verticle,在Verticle的start方法中寫我們的測試代碼,最終如下。

        ab5ea12b9025c12da2e1f4df0c4f19db.webp

        運(yùn)行結(jié)果會(huì)按新增、修改、查詢、刪除的順序執(zhí)行嗎?

        不會(huì)的!這四個(gè)方法會(huì)同時(shí)執(zhí)行,誰先搶到CPU的時(shí)間片,誰就先執(zhí)行。因?yàn)檫@四個(gè)方法都是異步的。

        新增(基礎(chǔ))

        update方法接收兩個(gè)參數(shù),第一個(gè)參數(shù)是我們要執(zhí)行的SQL。第二個(gè)參數(shù)是HandlerHandler<AsyncResult<UpdateResult>> handler。這種參數(shù)我們不是第一次遇見了,后續(xù)我會(huì)帶大家分析下Handler的實(shí)現(xiàn)機(jī)制。Handler中有一個(gè)泛型參數(shù)AsyncResult,標(biāo)識(shí)的是異步結(jié)果,也有一個(gè)泛型參數(shù)UpdateResult,這個(gè)參數(shù)就是異步方法執(zhí)行完畢后響應(yīng)的數(shù)據(jù)。

        update方法進(jìn)行了重載,可以在SQL和Handler中間傳入一個(gè)JsonArray。對(duì)于SQL中帶有參數(shù)的情景,在SQL中用“?”進(jìn)行替代,然后將參數(shù)的值放到JsonArray中,傳入到update方法中即可。參考后續(xù)修改操作的代碼!

        private void save(JDBCClient client) {    String sql = "insert into user(name) values ('zhangsan')";    client.update(sql, r -> {        if (r.succeeded()) {            System.out.println("保存成功");        }    });}

        修改(SQL中帶參數(shù))

        代碼結(jié)構(gòu)和上面的新增是完全相同的,這里演示了SQL中帶參數(shù)是情景,這種情景在開發(fā)中會(huì)用的更多。

        private void update(JDBCClient client) {    String sql = "update user set name = ? where id = ?";    // 帶參數(shù)    JsonArray params = new JsonArray()            .add("lisi")            .add(1);    client.updateWithParams(sql, params, r -> {        if (r.succeeded()) {            System.out.println("刪除成功");        }    });}

        刪除

        這里只是換了SQL,其他的都不變。

        private void delete(JDBCClient client) {    String sql = "delete from user";    client.update(sql, r -> {        if (r.succeeded()) {            System.out.println("刪除成功");        }    });}

        查詢

        查詢主要關(guān)注點(diǎn)是結(jié)果的封裝。

        1. 多行多列 :queryWithParams List<JsonObject>

        2. 單行多列:querySingleWithParams JsonArray

        3. 多行單列:同多行多列

        4. 單行單列:同單行多列

        我習(xí)慣性都用queryWithParams,然后對(duì)結(jié)果進(jìn)行處理后拿到想要的數(shù)據(jù),下面是一個(gè)單行多列的例子。

        private void query(JDBCClient client) {    String sql = "select * from sys_user where id = ?";    JsonArray params = new JsonArray().add(1);    client.queryWithParams(sql, params, r -> {        if (r.succeeded()) {            List<JsonObject> rows = r.result().getRows();            System.out.println(rows.get(0));        } else {            System.out.println(r.cause());        }    });}


        CRUD小結(jié)

        通過上面的代碼,可以很輕松的看到,基本的增刪改查還是比較簡單的。貼近于原生的寫法。

        對(duì)于剛開始接觸Vert.x的朋友,一定感覺非常不適應(yīng),首先是這么原始的嗎,我們之前都jpa了,這是哪個(gè)年代的框架。第二個(gè)就是“對(duì)象”呢?為啥用JsonObject啊,我怎么知道JsonObject里面到底有哪些屬性呢?

        對(duì)于這兩個(gè)問題我現(xiàn)在沒法給到具體的答案,畢竟用過DbUtils的朋友可能也不多,靈活和性能也未必體會(huì)的到。對(duì)于數(shù)據(jù)封裝到JsonObject中,這個(gè)真是利弊都有,禍兮福所倚,福兮禍所伏。福禍相依,有無相生,難易相成,長短相形,高下相傾,音聲相和,前后相隨。

        到這里數(shù)據(jù)庫操作就結(jié)束了嗎?沒有,還有兩個(gè)很難解決的問題。

        • 循環(huán)執(zhí)行多條SQL該怎么處理呢?

        • 如何執(zhí)行事務(wù)操作?

        這兩個(gè)問題都不好解決,而且寫起來都非常的惡心,我們挨個(gè)的看下吧。


        循環(huán)執(zhí)行SQL

        循環(huán)執(zhí)行有幾種表現(xiàn)形式

        • 循環(huán)相同的SQL,參數(shù)不同,且參數(shù)提前可以確定

        • 循環(huán)相同的SQL,次數(shù)在可控范圍之內(nèi)

        • 循環(huán)相同的SQL,參數(shù)不同,參數(shù)依賴上一個(gè)SQL的結(jié)果

        對(duì)于前兩種形式,都很好處理,第一種可以使用updateBatch提供的批量操作。第二種既然是次數(shù)可控,那就用Vert.x提供的fluent編程風(fēng)格,一個(gè)接一個(gè)的處理,雖然low,但也能解決問題。最麻煩的是第三種形式。

        批量執(zhí)行相同SQL

        批量執(zhí)行相同SQL,可以通過batchWithParams方法,這個(gè)方法中就接收了執(zhí)行的SQL和SQL所依賴的參數(shù)。這個(gè)參數(shù)的形式是List<JsonArray>。那么這樣我們就能很輕松的解決批量的問題。

        按照面向?qū)ο蟮乃季S方式,我們期望直接通過client.batchWithParams,當(dāng)你這么寫的時(shí)候,你會(huì)發(fā)現(xiàn)找不到這個(gè)方法。其實(shí),這個(gè)方法是在SQLConnection對(duì)象中的,因此,我們要先來獲得連接對(duì)象。

        7688e25c5d8ffd729f1edb2b21ff165a.webp

        上面的例子中,會(huì)執(zhí)行兩次SQL,參數(shù)是List集合里的兩個(gè)JsonArray元素。

        批量執(zhí)行的SQL,執(zhí)行次數(shù)在可控范圍

        Vert.x提供了Fluent編程風(fēng)格的API,就是說update方法后還可以繼續(xù)update方法,直到最后一個(gè)操作執(zhí)行完畢。如果有5個(gè)操作,那們就是5個(gè)update就可以了。這種方式雖然顯得low,但也能解決問題。

        94f035a60fa18c2c6e57c18cd46b584e.webp

        這種操作方式無法實(shí)現(xiàn)下一個(gè)操的條件是上一個(gè)操作的結(jié)果。所有的操作必須是沒有依賴的,誰先執(zhí)行都可以。

        批量的SQL,下一個(gè)SQL依賴上一個(gè)SQL的結(jié)果

        這是異步帶來的問題,就是循環(huán),且依賴的問題。因?yàn)楫惒巾憫?yīng)結(jié)果是在回調(diào)里面的,不是同步的響應(yīng),因此簡單的批量,你并不能通過一個(gè)for來解決。按照思路,就是在結(jié)果中再去封裝結(jié)果,因?yàn)槟悴恢烙卸嗌賹忧短?,所有這樣是顯然不可行的,那改如何解決呢?

        可以通過rxjava,這里簡單提供一種實(shí)現(xiàn)的思路,非常復(fù)雜。

        1、引入rxjava的依賴

        <dependency>    <groupId>io.vertx</groupId>    <artifactId>vertx-rx-java</artifactId>    <version>${vertx.version}</version></dependency>

        2、核心執(zhí)行程序

        7317269f6cd86de5845fcfbf240c59e2.webp

        3、rxExecuter方法實(shí)現(xiàn)

        adf8dee0048bc87c7145e4a26a06acaf.webp

        4、execute方法實(shí)現(xiàn)

        93554de42eb6ad99b5c05dd4f5027312.webp

        到這里,最為強(qiáng)大的循環(huán)就完事了,是不是比你想象的要復(fù)雜很多。沒辦法,想要循環(huán)異步的操作就要這么實(shí)現(xiàn)。


        事務(wù)

        數(shù)據(jù)庫操作,事務(wù)是不可避免的,先來回顧下jdbc如何開啟事務(wù)?

        1. 獲取連接

        2. 設(shè)置不自動(dòng)提交事務(wù)(setAutoCommit?= false)

        3. 執(zhí)行SQL操作

        4. 提交或者回滾事務(wù)

        5. 關(guān)閉資源

        那么我們分析下上面的這幾個(gè)步驟,在同步的編程模型下,每個(gè)步驟都很簡單,幾乎一行代碼就搞定了??墒堑搅水惒侥P拖?,我們首先應(yīng)該考慮的就是這個(gè)操作是不是一個(gè)耗時(shí)操作(這里簡單認(rèn)為通過網(wǎng)絡(luò)連接的,就是耗時(shí)操作),如果是耗時(shí)操作,我們就需要對(duì)其進(jìn)行異步的封裝。

        那么上面的5個(gè)步驟中,都需要和數(shù)據(jù)庫通信,也就需要走網(wǎng)絡(luò),網(wǎng)絡(luò)相比較CPU的處理速度那真是太慢了,因此肯定都是耗時(shí)操作。那也就意味著,這些都是異步的。

        /** * 事務(wù) * <p> * 獲取連接 * 設(shè)置不自動(dòng)提交事務(wù)(setAutoCommit = false) * 執(zhí)行SQL操作 * 提交或者回滾事務(wù) * 關(guān)閉資源 * * @param client */private void tx(JDBCClient client) {  client.getConnection(c->{    if(c.succeeded()) {      SQLConnection connection = c.result();      // 關(guān)閉事務(wù)自動(dòng)提交      connection.setAutoCommit(false, a->{        if(a.succeeded()) {          // 執(zhí)行SQL          connection.update("sql1", r1->{            if(r1.succeeded()) {              connection.update("sql2",r2->{                if(r2.succeeded()) {                  // 提交事務(wù)                  connection.commit(o->{});                } else {                  // 操作2執(zhí)行失敗,事務(wù)回滾                  connection.rollback(r->{});                }              });            }else {              // 操作1執(zhí)行失敗              connection.rollback(b->{                if(b.succeeded()) {                  // 回滾成功                }              });            }          });        } else {          // 開始事務(wù)失敗        }      });    } else {      // 獲取連接失敗    }  });}

        沒有辦法,這里就是這樣,開啟事務(wù)也就只能這么實(shí)現(xiàn),著實(shí)非常麻煩。這里同樣可以使用rx來降低開發(fā)難度。


        總結(jié)

        到這里,相信你對(duì)數(shù)據(jù)庫的操作就已經(jīng)很熟悉了。習(xí)慣了Mybatis或者JPA的你,是不是一時(shí)還不能接受。你是不是還在想著我該先創(chuàng)建個(gè)對(duì)象,然后將結(jié)果封裝到對(duì)象里。

        上面的想法是完全可以實(shí)現(xiàn)的,但是,用了很久的jdbc之后,我會(huì)感覺到,純的jdbc未必不是個(gè)好的寫法。我在一些公司的小的項(xiàng)目架構(gòu)中,也會(huì)采用Spring+DbUtils的形式,在簡單、靈活以及性能的方面也是提高了不少。

        所以拋開技術(shù)實(shí)現(xiàn)不談,還是要貼合業(yè)務(wù)需求。存在的即合理的。

        The End

        下期預(yù)告

        我們系統(tǒng)中操作比較頻繁的除了數(shù)據(jù)庫,就屬于Redis了,幾乎所有的項(xiàng)目都離不開高性能的緩存服務(wù)器Redis。下一篇我來帶大家看看Vert.x是如何和Redis進(jìn)行交互的。


        瀏覽 98
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            www.精品在线 | 九色PORNY自拍蝌蚪成人 午夜男女视频 | 国产黄色网络 | 国产成人欧美 | 少妇张开双腿迎合小坏蛋粗大 | 国产又猛又粗又爽的视频 | 91精品综合久久久久久五月丁香 | 日本一区二区精品91豆花 | 非洲的黄色片 | 亚洲小电影 |