首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Vert.x 異步 JDBC 實戰:高效資料庫存取完整指南

     編輯:Java 相關知識

前言

Vert.x 的核心優勢在於非阻塞異步模型。傳統 JDBC 同步阻塞操作會佔用事件循環線程,嚴重影響吞吐量。vertx-jdbc-client 提供異步 API,讓資料庫操作不阻塞事件循環,充分發揮 Vert.x 高併發優勢。

專案依賴

<dependency>
    <groupId>io.vertx</groupId>
    <artifactId>vertx-jdbc-client</artifactId>
    <version>4.5.0</version>  
</dependency>
<dependency>
    <groupId>org.hsqldb</groupId>
    <artifactId>hsqldb</artifactId>
    <version>2.7.1</version>
</dependency>

核心概念:異步結果處理

Vert.x 使用 Handler<AsyncResult<T>> 模式處理異步結果:

// 同步風格 (阻塞)
ResultSet rs = stmt.executeQuery("SELECT * FROM Products");

// 異步風格 (非阻塞)
connection.query("SELECT * FROM Products", result -> {
    if (result.succeeded()) {
        List rows = result.result().getRows();
        // 處理結果...
    } else {
        result.cause().printStackTrace();
    }
});

JDBC Client 初始化與配置

public class MainVerticle extends AbstractVerticle {
    private JDBCClient jdbcClient;
    
    @Override
    public void start(Promise startPromise) {
        // 從配置讀取資料庫參數
        JsonObject config = config();
        
        // 建立共享連接池
        jdbcClient = JDBCClient.createShared(vertx, config, "My-Whisky-Collection");
        
        // 啟動流程:連接 → 初始化資料 → 啟動 Web 服務
        startBackend(conn -> 
            createSomeData(conn, nothing -> 
                startWebApp(http -> 
                    completeStartup(http, startPromise), startPromise), startPromise), 
            startPromise);
    }
    
    private void startBackend(Handler> next, Promise fut) {
        jdbcClient.getConnection(ar -> {
            if (ar.failed()) {
                fut.fail(ar.cause());
            } else {
                next.handle(Future.succeededFuture(ar.result()));
            }
        });
    }
}

配置檔範例

{
  "url": "jdbc:hsqldb:mem:db?shutdown=true",
  "driver_class": "org.hsqldb.jdbcDriver",
  "max_pool_size": 30,
  "user": "SA",
  "password": ""
}

資料庫操作完整範例

1. 建表與初始資料

private void createSomeData(AsyncResult result, 
                           Handler> next, Promise fut) {
    if (result.failed()) {
        fut.fail(result.cause());
        return;
    }
    
    SQLConnection conn = result.result();
    
    // 建表
    conn.execute("CREATE TABLE IF NOT EXISTS Whisky (id INTEGER IDENTITY, name VARCHAR(100), origin VARCHAR(100))", 
        ar -> {
            if (ar.failed()) {
                fut.fail(ar.cause());
                closeQuietly(conn);
                return;
            }
            
            // 檢查是否有資料
            conn.query("SELECT * FROM Whisky", select -> {
                if (select.failed()) {
                    fut.fail(select.cause());
                    closeQuietly(conn);
                    return;
                }
                
                if (select.result().getNumRows() == 0) {
                    // 插入初始資料
                    insert(new Whisky("Bowmore 15 Years Laimrig", "Scotland, Islay"), conn, v -> 
                        insert(new Whisky("Talisker 57° North", "Scotland, Island"), conn, r -> {
                            next.handle(Future.succeededFuture());
                            closeQuietly(conn);
                        }));
                } else {
                    next.handle(Future.succeededFuture());
                    closeQuietly(conn);
                }
            });
        });
}

private void insert(Whisky whisky, SQLConnection conn, Handler> next) {
    String sql = "INSERT INTO Whisky (name, origin) VALUES (?, ?)";
    conn.updateWithParams(sql, new JsonArray().add(whisky.getName()).add(whisky.getOrigin()), ar -> {
        if (ar.failed()) {
            next.handle(Future.failedFuture(ar.cause()));
            return;
        }
        // 取得自動生成的 ID
        int id = ar.result().getKeys().getInteger(0);
        next.handle(Future.succeededFuture(new Whisky(id, whisky.getName(), whisky.getOrigin())));
    });
}

2. REST API 端點實作

private void getAll(RoutingContext rc) {
    jdbcClient.getConnection(ar -> {
        if (ar.failed()) {
            rc.fail(ar.cause());
            return;
        }
        SQLConnection conn = ar.result();
        conn.query("SELECT * FROM Whisky", result -> {
            if (result.failed()) {
                rc.fail(result.cause());
            } else {
                List whiskies = result.result().getRows().stream()
                    .map(Whisky::new)
                    .collect(Collectors.toList());
                rc.response()
                    .putHeader("Content-Type", "application/json; charset=utf-8")
                    .end(Json.encodePrettily(whiskies));
            }
            closeQuietly(conn); // 必須釋放連接回池
        });
    });
}

// 其他 CRUD 操作類似...
private void getOne(RoutingContext rc) { /* ... */ }
private void addOne(RoutingContext rc) { /* ... */ }
private void updateOne(RoutingContext rc) { /* ... */ }
private void deleteOne(RoutingContext rc) { /* ... */ }

關鍵最佳實踐

1. 連接管理鐵律

  • 獲取連接 → 操作 → 釋放連接,缺一不可
  • 使用 try-with-resources 風格或 closeQuietly() 確保釋放
  • 異常路徑同樣要釋放連接

2. 參數化查詢防 SQL 注入

// ✅ 正確:使用 updateWithParams / queryWithParams
conn.updateWithParams("INSERT INTO t (col) VALUES (?)", new JsonArray().add(userInput));

// ❌ 錯誤:字串拼接
conn.update("INSERT INTO t (col) VALUES ('" + userInput + "')");

3. 事務處理

jdbcClient.getConnection(ar -> {
    SQLConnection conn = ar.result();
    conn.setAutoCommit(false, txAr -> {
        // 執行多條 SQL...
        conn.commit(commitAr -> {
            if (commitAr.failed()) {
                conn.rollback(rollbackAr -> closeQuietly(conn));
            } else {
                closeQuietly(conn);
            }
        });
    });
});

4. 優雅關閉

@Override
public void stop(Promise stopPromise) {
    jdbcClient.close(stopPromise); // 關閉連接池
}

測試配置

// 單元測試配置 (內存資料庫)
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"));

遷移到生產環境

{
  "url": "jdbc:postgresql://db-host:5432/myapp",
  "driver_class": "org.postgresql.Driver",
  "max_pool_size": 50,
  "user": "app_user",
  "password": "${DB_PASSWORD}"  // 從環境讀取
}

總結

Vert.x 異步 JDBC 讓資料庫操作不再阻塞事件循環,配合連接池與非阻塞 API,能構建高吞吐、低延遲的反應式應用。關鍵在於:掌握 Handler 模式、嚴格管理連接生命週期、善用參數化查詢。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved