透過 C++ 適用的 Firebase 即時資料庫節省資料

開始使用

如果您尚未設定應用程式並存取資料庫,請先參閱 Get Started 指南。

取得 DatabaseReference

如要將資料寫入資料庫,您需要 DatabaseReference 的例項:

    // Get the root reference location of the database.
    firebase::database::DatabaseReference dbref = database->GetReference();

儲存資料

將資料寫入 Firebase Realtime Database 的方法有四種:

方法 常見的使用方式
SetValue() 將資料寫入或取代至定義的路徑 (例如 users/<user-id>/<username>)。
PushChild() 新增至資料清單。每次呼叫 Push() 時,Firebase 都會產生專屬金鑰,這也是可用來做為專屬 ID 的 user-scores/<user-id>/<unique-score-id>
UpdateChildren() 更新已定義路徑的部分金鑰,而不會取代所有資料。
RunTransaction() 更新可能因並行更新而損毀的複雜資料。

在參照資料中寫入、更新或刪除資料

基本寫入作業

對於基本的寫入作業,您可以使用 SetValue() 將資料儲存至指定的參照,取代該路徑上的任何現有資料。您可以使用這個方法,透過支援下列項目的 Variant 類型傳遞 JSON 接受的類型:

  • 空值 (這會刪除資料)
  • 整數 (64 位元)
  • 雙精度浮點數
  • 布林值
  • 字串
  • 變化版本向量
  • 字串與變化版本的對應

以這種方式使用 SetValue() 會覆寫指定位置的資料,包括任何子節點。不過,您還是可以更新子項,而無須重寫整個物件。如果您想允許使用者更新個人資料,可以按照下列方式更新使用者名稱:

dbref.Child("users").Child(userId).Child("username").SetValue(name);

將資料附加至資料清單

使用 PushChild() 方法,將資料附加至多用戶端應用程式中的清單。每次在指定 Firebase 參照資料中新增子項時,PushChild() 方法都會產生專屬索引鍵。只要為清單中的每個新元素使用這些自動產生的鍵,多個用戶端就能同時將子項新增至相同位置,而不會發生寫入衝突。PushChild() 產生的專屬索引是根據時間戳記,因此清單項目會自動依時間先後排序。

您可以使用 PushChild() 方法傳回的新資料參照,取得子項的自動產生鍵值,或為子項設定資料。對 PushChild() 參照呼叫 GetKey() 會傳回自動產生鍵的值。

更新特定欄位

如要同時寫入節點的特定子項,而不覆寫其他子項,請使用 UpdateChildren() 方法。

呼叫 UpdateChildren() 時,您可以指定索引鍵的路徑,藉此更新較低層級的子項值。如果資料儲存在多個位置,可讓資料更容易擴充,您可以使用資料分支更新該資料的所有例項。舉例來說,遊戲可能會有類似以下的 LeaderboardEntry 類別:

class LeaderboardEntry {
  std::string uid;
  int score = 0;

 public:
  LeaderboardEntry() {
  }

  LeaderboardEntry(std::string uid, int score) {
    this->uid = uid;
    this->score = score;
  }

  std::map<std::string, Object> ToMap() {
    std::map<string, Variant> result = new std::map<string, Variant>();
    result["uid"] = Variant(uid);
    result["score"] = Variant(score);

    return result;
  }
}

如要建立 LeaderboardEntry,並同時將其更新至最近的分數動態消息和使用者自己的分數清單,遊戲會使用以下程式碼:

void WriteNewScore(std::string userId, int score) {
  // Create new entry at /user-scores/$userid/$scoreid and at
  // /leaderboard/$scoreid simultaneously
  std::string key = dbref.Child("scores").PushChild().GetKey();
  LeaderBoardEntry entry = new LeaderBoardEntry(userId, score);
  std::map<std::string, Variant> entryValues = entry.ToMap();

  std::map<string, Variant> childUpdates = new std::map<string, Variant>();
  childUpdates["/scores/" + key] = entryValues;
  childUpdates["/user-scores/" + userId + "/" + key] = entryValues;

  dbref.UpdateChildren(childUpdates);
}

這個範例使用 PushChild() 在節點中建立項目,其中包含 /scores/$key 上所有使用者的項目,並同時使用 key() 擷取鍵。接著,您可以使用這個鍵,在使用者的分數 /user-scores/$userid/$key 中建立第二個項目。

透過這些路徑,您可以透過一次呼叫 UpdateChildren() 來同時更新 JSON 樹狀結構中的多個位置,例如這個範例如何在兩個位置建立新項目。以這種方式進行的同時更新作業是不可分割的:所有更新都成功或都失敗。

刪除資料

刪除資料最簡單的方法,就是在資料位置的參照上呼叫 RemoveValue()

您也可以指定 null Variant 做為其他寫入作業 (例如 SetValue()UpdateChildren()) 的值,藉此刪除資料。您可以將這項技巧與 UpdateChildren() 搭配使用,在單一 API 呼叫中刪除多個子項。

掌握資料提交的時間。

如要瞭解資料何時提交至 Firebase Realtime Database 伺服器,請檢查 Future 結果是否成功。

將資料儲存為交易

如果要處理可能因並行修改而毀損的資料 (例如遞增計數器),您可以使用交易作業。您可以為這項作業指定 DoTransaction 函式。這個更新函式會將資料目前的狀態做為引數,並傳回您要寫入的新狀態。如果其他用戶端在您成功寫入新值之前寫入該位置,系統會再次以新的目前值呼叫更新函式,並重試寫入作業。

舉例來說,您可以在遊戲中允許使用者更新排行榜,顯示前五名最高分:

void AddScoreToLeaders(std::string email,
                       long score,
                       DatabaseReference leaderBoardRef) {
  leaderBoardRef.RunTransaction([](firebase::database::MutableData* mutableData) {
    if (mutableData.children_count() >= MaxScores) {
      long minScore = LONG_MAX;
      MutableData *minVal = null;
      std::vector<MutableData> children = mutableData.children();
      std::vector<MutableData>::iterator it;
      for (it = children.begin(); it != children.end(); ++it) {
        if (!it->value().is_map())
          continue;
        long childScore = (long)it->Child("score").value().int64_value();
        if (childScore < minScore) {
          minScore = childScore;
          minVal = &*it;
        }
      }
      if (minScore > score) {
        // The new score is lower than the existing 5 scores, abort.
        return kTransactionResultAbort;
      }

      // Remove the lowest score.
      children.Remove(minVal);
    }

    // Add the new high score.
    std::map<std::string, Variant> newScoreMap =
      new std::map<std::string, Variant>();
    newScoreMap["score"] = score;
    newScoreMap["email"] = email;
    children.Add(newScoreMap);
    mutableData->set_value(children);
    return kTransactionResultSuccess;
  });
}

如果有多位使用者同時記錄分數,或是用戶端有過時資料,使用交易可避免排行榜出現錯誤。如果交易遭到拒絕,伺服器會將目前的值傳回給用戶端,後者會使用更新後的值再次執行交易。這項作業會重複執行,直到交易獲得接受或嘗試次數過多為止。

離線寫入資料

如果用戶端失去網路連線,應用程式仍會繼續正常運作。

每個連線至 Firebase 資料庫的用戶端都會維護任何有效資料的內部版本。資料寫入時,系統會先將資料寫入這個本機版本。接著,Firebase 用戶端會以「盡力而為」的方式,將這些資料與遠端資料庫伺服器和其他用戶端同步。

因此,所有寫入資料庫的作業都會立即觸發本機事件,然後才將任何資料寫入伺服器。也就是說,無論網路延遲或連線狀況為何,應用程式都能持續回應。

重新建立連線後,應用程式會收到適當的事件組合,讓用戶端與目前的伺服器狀態保持同步,而無需撰寫任何自訂程式碼。

後續步驟