Lưu dữ liệu bằng Cơ sở dữ liệu theo thời gian thực của Firebase cho C++

Bắt đầu

Trước tiên, hãy xem hướng dẫn Get Started nếu bạn chưa thiết lập ứng dụng và quyền truy cập vào cơ sở dữ liệu.

Lấy DatabaseReference

Để ghi dữ liệu vào Cơ sở dữ liệu, bạn cần có một thực thể của DatabaseReference:

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

Lưu dữ liệu

Có 4 phương thức để ghi dữ liệu vào Firebase Realtime Database:

Phương thức Các cách dùng phổ biến
SetValue() Ghi hoặc thay thế dữ liệu vào một đường dẫn đã xác định, chẳng hạn như users/<user-id>/<username>.
PushChild() Thêm vào danh sách dữ liệu. Mỗi khi bạn gọi Push(), Firebase sẽ tạo một khoá duy nhất cũng có thể dùng làm giá trị nhận dạng duy nhất, chẳng hạn như user-scores/<user-id>/<unique-score-id>.
UpdateChildren() Cập nhật một số khoá cho một đường dẫn đã xác định mà không cần thay thế tất cả dữ liệu.
RunTransaction() Cập nhật dữ liệu phức tạp có thể bị hỏng do các bản cập nhật đồng thời.

Ghi, cập nhật hoặc xoá dữ liệu tại một tham chiếu

Các thao tác ghi cơ bản

Đối với các thao tác ghi cơ bản, bạn có thể dùng SetValue() để lưu dữ liệu vào một giá trị tham chiếu đã chỉ định, thay thế mọi dữ liệu hiện có tại đường dẫn đó. Bạn có thể sử dụng phương thức này để truyền các loại được JSON chấp nhận thông qua một loại Biến hỗ trợ:

  • Null (thao tác này sẽ xoá dữ liệu)
  • Số nguyên (64 bit)
  • Số có dấu phẩy động có độ chính xác gấp đôi
  • Boolean
  • Dây
  • Vectơ của biến thể
  • Ánh xạ các chuỗi với Biến thể

Khi sử dụng SetValue() theo cách này, dữ liệu sẽ bị ghi đè tại vị trí được chỉ định, bao gồm cả mọi nút con. Tuy nhiên, bạn vẫn có thể cập nhật một phần tử con mà không cần viết lại toàn bộ đối tượng. Nếu muốn cho phép người dùng cập nhật hồ sơ của họ, bạn có thể cập nhật tên người dùng như sau:

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

Thêm vào danh sách dữ liệu

Sử dụng phương thức PushChild() để thêm dữ liệu vào danh sách trong các ứng dụng nhiều người dùng. Phương thức PushChild() sẽ tạo một khoá duy nhất mỗi khi một phần tử con mới được thêm vào tham chiếu Firebase đã chỉ định. Bằng cách sử dụng các khoá được tạo tự động này cho mỗi phần tử mới trong danh sách, một số ứng dụng có thể thêm các phần tử con vào cùng một vị trí cùng lúc mà không xảy ra xung đột ghi. Khoá duy nhất do PushChild() tạo dựa trên dấu thời gian, vì vậy, các mục trong danh sách sẽ tự động được sắp xếp theo trình tự thời gian.

Bạn có thể dùng thông tin tham chiếu đến dữ liệu mới do phương thức PushChild() trả về để lấy giá trị của khoá do hệ thống tự động tạo của đối tượng con hoặc đặt dữ liệu cho đối tượng con. Việc gọi GetKey() trên một giá trị tham chiếu PushChild() sẽ trả về giá trị của khoá được tạo tự động.

Cập nhật các trường cụ thể

Để ghi đồng thời vào các phần tử con cụ thể của một nút mà không ghi đè các nút con khác, hãy sử dụng phương thức UpdateChildren().

Khi gọi UpdateChildren(), bạn có thể cập nhật các giá trị con ở cấp thấp hơn bằng cách chỉ định một đường dẫn cho khoá. Nếu dữ liệu được lưu trữ ở nhiều vị trí để mở rộng quy mô tốt hơn, bạn có thể cập nhật tất cả các phiên bản của dữ liệu đó bằng cách sử dụng phân phối dữ liệu. Ví dụ: một trò chơi có thể có một lớp LeaderboardEntry như sau:

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

 public:
  LeaderboardEntry() {
  }

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

  std::map&ltstd::string, Object&gt ToMap() {
    std::map&ltstring, Variant&gt result = new std::map&ltstring, Variant&gt();
    result["uid"] = Variant(uid);
    result["score"] = Variant(score);

    return result;
  }
}

Để tạo một LeaderboardEntry và đồng thời cập nhật LeaderboardEntry đó vào nguồn cấp dữ liệu điểm số gần đây và danh sách điểm số của riêng người dùng, trò chơi sẽ sử dụng mã sau:

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&ltstd::string, Variant&gt entryValues = entry.ToMap();

  std::map&ltstring, Variant&gt childUpdates = new std::map&ltstring, Variant&gt();
  childUpdates["/scores/" + key] = entryValues;
  childUpdates["/user-scores/" + userId + "/" + key] = entryValues;

  dbref.UpdateChildren(childUpdates);
}

Ví dụ này sử dụng PushChild() để tạo một mục trong nút chứa các mục cho tất cả người dùng tại /scores/$key và đồng thời truy xuất khoá bằng key(). Sau đó, khoá này có thể được dùng để tạo mục nhập thứ hai trong điểm số của người dùng tại /user-scores/$userid/$key.

Khi sử dụng các đường dẫn này, bạn có thể thực hiện các bản cập nhật đồng thời cho nhiều vị trí trong cây JSON bằng một lệnh gọi duy nhất đến UpdateChildren(), chẳng hạn như cách ví dụ này tạo mục nhập mới ở cả hai vị trí. Các bản cập nhật đồng thời được thực hiện theo cách này có tính chất không thể phân chia: hoặc tất cả các bản cập nhật đều thành công hoặc tất cả các bản cập nhật đều không thành công.

Xóa dữ liệu

Cách đơn giản nhất để xoá dữ liệu là gọi RemoveValue() trên một tham chiếu đến vị trí của dữ liệu đó.

Bạn cũng có thể xoá bằng cách chỉ định null Variant làm giá trị cho một thao tác ghi khác, chẳng hạn như SetValue() hoặc UpdateChildren(). Bạn có thể sử dụng kỹ thuật này với UpdateChildren() để xoá nhiều phần tử con trong một lệnh gọi API duy nhất.

Biết được thời điểm dữ liệu của bạn được xác nhận.

Để biết thời điểm dữ liệu của bạn được cam kết với máy chủ Firebase Realtime Database, hãy kiểm tra kết quả Future để biết thành công.

Lưu dữ liệu dưới dạng giao dịch

Khi làm việc với dữ liệu có thể bị hỏng do các hoạt động sửa đổi đồng thời, chẳng hạn như bộ đếm tăng dần, bạn có thể sử dụng thao tác giao dịch. Bạn cung cấp cho thao tác này một hàm DoTransaction. Hàm cập nhật này lấy trạng thái hiện tại của dữ liệu làm đối số và trả về trạng thái mong muốn mới mà bạn muốn ghi. Nếu một ứng dụng khác ghi vào vị trí này trước khi giá trị mới của bạn được ghi thành công, thì hàm cập nhật sẽ được gọi lại bằng giá trị hiện tại mới và thao tác ghi sẽ được thử lại.

Ví dụ: trong một trò chơi, bạn có thể cho phép người dùng cập nhật bảng xếp hạng với 5 điểm số cao nhất:

void AddScoreToLeaders(std::string email,
                       long score,
                       DatabaseReference leaderBoardRef) {
  leaderBoardRef.RunTransaction([](firebase::database::MutableData* mutableData) {
    if (mutableData.children_count() &gt= MaxScores) {
      long minScore = LONG_MAX;
      MutableData *minVal = null;
      std::vector&ltMutableData&gt children = mutableData.children();
      std::vector&ltMutableData&gt::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 &lt minScore) {
          minScore = childScore;
          minVal = &amp*it;
        }
      }
      if (minScore &gt 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&ltstd::string, Variant&gt newScoreMap =
      new std::map&ltstd::string, Variant&gt();
    newScoreMap["score"] = score;
    newScoreMap["email"] = email;
    children.Add(newScoreMap);
    mutableData->set_value(children);
    return kTransactionResultSuccess;
  });
}

Việc sử dụng một giao dịch sẽ ngăn bảng xếp hạng bị sai nếu nhiều người dùng ghi lại điểm số cùng lúc hoặc máy khách có dữ liệu cũ. Nếu giao dịch bị từ chối, máy chủ sẽ trả về giá trị hiện tại cho máy khách. Máy khách này sẽ chạy lại giao dịch với giá trị đã cập nhật. Quá trình này lặp lại cho đến khi giao dịch được chấp nhận hoặc bạn đã thực hiện quá nhiều lần.

Ghi dữ liệu khi không có mạng

Nếu một máy khách mất kết nối mạng, ứng dụng của bạn sẽ tiếp tục hoạt động bình thường.

Mọi ứng dụng khách được kết nối với cơ sở dữ liệu Firebase đều duy trì phiên bản nội bộ riêng của mọi dữ liệu đang hoạt động. Khi dữ liệu được ghi, dữ liệu sẽ được ghi vào phiên bản cục bộ này trước. Sau đó, ứng dụng Firebase sẽ đồng bộ hoá dữ liệu đó với các máy chủ cơ sở dữ liệu từ xa và với các ứng dụng khác theo cách "cố gắng hết sức".

Do đó, tất cả các thao tác ghi vào cơ sở dữ liệu sẽ kích hoạt ngay các sự kiện cục bộ, trước khi bất kỳ dữ liệu nào được ghi vào máy chủ. Điều này có nghĩa là ứng dụng của bạn vẫn phản hồi bất kể độ trễ hoặc khả năng kết nối mạng.

Sau khi kết nối được thiết lập lại, ứng dụng của bạn sẽ nhận được bộ sự kiện thích hợp để ứng dụng đồng bộ hoá với trạng thái hiện tại của máy chủ mà không cần phải viết bất kỳ mã tuỳ chỉnh nào.

Các bước tiếp theo