使用 GoogleSQL for Bigtable 開發應用程式
您可以使用 Bigtable 適用的 GoogleSQL,從應用程式執行查詢,將運算函式卸載至 Bigtable。這樣一來,用戶端處理作業的需求就會減少。GoogleSQL 是符合 ANSI 標準的結構化查詢語言,適用於 Bigtable 和其他 Google Cloud 服務。如要進一步瞭解 GoogleSQL for Bigtable 的其他用途,請參閱 GoogleSQL for Bigtable 總覽。
事前準備
請確認您已具備以下條件:
- Bigtable 執行個體和資料表。
- 您語言的 Bigtable 用戶端程式庫。如要進一步瞭解如何安裝及使用 Bigtable 的用戶端程式庫,請參閱「Bigtable 用戶端程式庫」。
- 透過應用程式預設憑證 (ADC) 設定驗證。詳情請參閱「設定本機開發環境的驗證機制」。
範例情境
本指南中的範例使用範例 weather-data 表格,您可以透過Google Cloud 控制台的免費試用執行個體存取該表格。假設資料表儲存來自不同氣象站的資訊,這些氣象站以 station_id 識別。每個測站都會在名為「temp_f」的資料欄中,以華氏溫度記錄溫度。您可以使用 SQL 查詢,直接在 Bigtable 中將這些溫度轉換為攝氏溫度,然後將結果傳回應用程式,不必在應用程式中計算溫度。如要進一步瞭解如何查詢天氣資料表,請參閱「查詢範例資料」。
最佳做法
如要提升效能及資源用量,開發應用程式時請考慮下列最佳做法:
- 重複使用用戶端:為應用程式建立單一
BigtableDataClient。用戶端會處理 gRPC 管道管理作業,並專為高並行設計。建議您在應用程式啟動後建立這個用戶端一次,然後重複用於每個查詢。 - 使用參數化查詢:使用
PreparedStatement將查詢邏輯與資料值分開。這可讓 Bigtable 快取執行計畫,並防止 SQL 注入。 - 重複使用預先準備好的陳述式:每個 SQL 字串只呼叫
PreparedStatement一次。在多個要求中儲存及重複使用陳述式。
建立 Bigtable 用戶端
如要處理應用程式與 Bigtable 之間的連線,請建立 BigtableDataClient。以下範例說明如何在應用程式中共用單一用戶端例項,有效管理連線:
BigtableDataSettings settings = BigtableDataSettings.newBuilder()
.setProject(PROJECT_ID)
.setInstance(INSTANCE_ID)
.build();
// The client is thread-safe and should be reused.
try (BigtableDataClient dataClient = BigtableDataClient.create(settings)) {
System.out.println("Connected to: " + INSTANCE_ID);
}
準備查詢
建議您使用參數化查詢,而非直接將資料放入 SQL 字串。在 Bigtable 的 GoogleSQL 中,參數化查詢等同於預先準備的陳述式。這樣一來,您就能將查詢邏輯與資料值分開。
以下範例說明如何使用參數預留位置 (例如 @sid) 定義 GoogleSQL 查詢。然後指定 SQL 字串和每個參數的型別,建立 PreparedStatement,以便下次更快執行查詢:
Map<String, SqlType<?>> paramTypes = new HashMap<>();
paramTypes.put("sid", SqlType.string());
String sql = "SELECT " +
"ROUND((CAST(CAST(weather['temp_f'] AS STRING) AS INT64) - 32) * 5 / 9, 2) AS temp_celsius " +
"FROM " + TABLE_NAME + " WHERE station_id = @sid LIMIT 1";
// Create and reuse the PreparedStatement.
PreparedStatement preparedStatement = dataClient.prepareStatement(sql, paramTypes);
執行查詢
提供參數值並執行查詢。以下範例說明如何使用 PreparedStatement 將值繫結至參數,以建立 BoundStatement。接著執行查詢,並逐一查看結果,列印 ResultSet,也就是攝氏溫度:
BoundStatement boundStatement = preparedStatement.bind()
.setStringParam("sid", stationId)
.build();
try (ResultSet resultSet = dataClient.executeQuery(boundStatement)) {
while (resultSet.next()) {
System.out.println("Temp: " + resultSet.getDouble("temp_celsius"));
}
}
檢查資料結構
開發或探索 SQL 時,建議您探索與資料表相關聯的中繼資料,瞭解資料結構。由於 Bigtable 具有彈性結構定義,如果您使用 SELECT * 查詢,查詢結果可能會因資料而異。因此建議您不要在正式環境應用程式中使用 SELECT * 查詢。
以下範例會查看結果集的中繼資料 (例如資料欄名稱和類型),然後將這些資料傳回給用戶端,與資料無關。這樣一來,您就能先取得資料欄資訊,然後建立應用程式中處理資料所需的資料結構。
ResultSetMetadata metadata = resultSet.getMetadata();
System.out.print("Columns: ");
metadata.getColumnsList().forEach(col -> System.out.print(col.getName() + " "));
System.out.println();
使用資料欄系列
Bigtable 會將資料儲存在資料欄系列中。GoogleSQL 會將這些系列傳回為名稱和值的清單,也就是資料欄限定符和值的對應。讀取資料欄系列時,Bigtable 會傳回 SqlType.Map 型別。從具有記錄的資料表讀取資料欄系列時 (例如 SELECT column FROM my_table(with_history=>true)),Bigtable 會傳回 SqlType.historicalMap() 型別,這是資料欄限定符的地圖,以及儲存格時間戳記和值的陣列。如需更多 SqlType 類型範例,請參閱 SqlType。
下列範例說明如何一次取得整組資料欄,而非一次取得一個資料欄。程式碼會將資料欄系列儲存在對應中,方便您逐一查看所有名稱和值:
Map<String, SqlType<?>> paramTypes = new HashMap<>();
paramTypes.put("keyPrefix", SqlType.bytes());
String sql = String.format("SELECT weather FROM %s WHERE STARTS_WITH(_key, @keyPrefix)", TABLE_NAME);
PreparedStatement preparedStatement = dataClient.prepareStatement(sql, paramTypes);
BoundStatement boundStatement = preparedStatement.bind()
.setBytesParam("keyPrefix", ByteString.copyFromUtf8(key))
.build();
try (ResultSet resultSet = dataClient.executeQuery(boundStatement)) {
while (resultSet.next()) {
SqlType.Map<ByteString, ByteString> mapType = SqlType.mapOf(SqlType.bytes(), SqlType.bytes());
Map<ByteString, ByteString> weatherValues = resultSet.getMap("weather", mapType);
for (Map.Entry<ByteString, ByteString> entry : weatherValues.entrySet()) {
System.out.printf("%s = %s\n",
entry.getKey().toStringUtf8(),
entry.getValue().toStringUtf8());
}
}
}