การบันทึกข้อมูลด้วยฐานข้อมูลเรียลไทม์ของ Firebase สำหรับ C++

เริ่มต้นใช้งาน

โปรดดูคำแนะนำ Get Started ก่อน หากยังไม่ได้ตั้งค่าแอปและสิทธิ์เข้าถึงฐานข้อมูล

รับ DatabaseReference

หากต้องการเขียนข้อมูลลงในฐานข้อมูล คุณต้องมีอินสแตนซ์ของ DatabaseReference

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

การบันทึกข้อมูล

วิธีการเขียนข้อมูลไปยัง Firebase Realtime Database มี 4 วิธีดังนี้

วิธีการ การใช้งานทั่วไป
SetValue() เขียนหรือแทนที่ข้อมูลไปยังเส้นทางที่กำหนด เช่น users/<user-id>/<username>
PushChild() เพิ่มลงในรายการข้อมูล ทุกครั้งที่คุณเรียกใช้ Push() Firebase จะสร้างคีย์ที่ไม่ซ้ำกันซึ่งใช้เป็น ตัวระบุที่ไม่ซ้ำกันได้ด้วย เช่น user-scores/<user-id>/<unique-score-id>
UpdateChildren() อัปเดตคีย์บางรายการสำหรับเส้นทางที่กำหนดโดยไม่ต้องแทนที่ข้อมูลทั้งหมด
RunTransaction() อัปเดตข้อมูลที่ซับซ้อนซึ่งอาจเสียหายจากการอัปเดตพร้อมกัน

เขียน อัปเดต หรือลบข้อมูลที่การอ้างอิง

การดำเนินการเขียนพื้นฐาน

สำหรับการดำเนินการเขียนพื้นฐาน คุณสามารถใช้ SetValue() เพื่อบันทึกข้อมูลไปยัง การอ้างอิงที่ระบุ โดยแทนที่ข้อมูลที่มีอยู่ที่เส้นทางนั้น คุณสามารถใช้วิธีนี้ เพื่อส่งประเภทที่ JSON ยอมรับผ่านประเภท Variant ซึ่งรองรับสิ่งต่อไปนี้

  • Null (ซึ่งจะลบข้อมูล)
  • จำนวนเต็ม (64 บิต)
  • เลขทศนิยมที่มีความแม่นยำแบบคู่
  • บูลีน
  • สตริง
  • เวกเตอร์ของตัวแปร
  • แมปของสตริงไปยังตัวแปร

การใช้ SetValue() ในลักษณะนี้จะเขียนทับข้อมูลในตำแหน่งที่ระบุ รวมถึงโหนดย่อยทั้งหมด แต่คุณยังอัปเดตออบเจ็กต์ย่อยได้โดยไม่ต้อง เขียนออบเจ็กต์ทั้งหมดใหม่ หากต้องการอนุญาตให้ผู้ใช้อัปเดตโปรไฟล์ คุณสามารถอัปเดตชื่อผู้ใช้ได้ดังนี้

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

ผนวกกับรายการข้อมูล

ใช้เมธอด PushChild() เพื่อต่อท้ายข้อมูลในรายการในแอปพลิเคชันแบบหลายผู้ใช้ เมธอด PushChild() จะสร้างคีย์ที่ไม่ซ้ำกันทุกครั้งที่มีการเพิ่ม บุตรหลานใหม่ลงในการอ้างอิง Firebase ที่ระบุ การใช้คีย์ที่สร้างขึ้นโดยอัตโนมัติเหล่านี้สำหรับองค์ประกอบใหม่แต่ละรายการในรายการ จะช่วยให้ไคลเอ็นต์หลายรายเพิ่มบุตรหลานไปยังสถานที่เดียวกันได้พร้อมกันโดยไม่มีข้อขัดแย้งในการเขียน คีย์ที่ไม่ซ้ำกันซึ่งสร้างโดย PushChild() จะอิงตามการประทับเวลา ดังนั้นรายการในลิสต์จะ จัดเรียงตามลำดับเวลาโดยอัตโนมัติ

คุณสามารถใช้การอ้างอิงไปยังข้อมูลใหม่ที่เมธอด PushChild() ส่งคืน เพื่อรับค่าของคีย์ที่สร้างขึ้นโดยอัตโนมัติของรายการย่อยหรือตั้งค่าข้อมูลสำหรับรายการย่อย การเรียก GetKey() ในการอ้างอิง PushChild() จะแสดงผลค่าของคีย์ที่สร้างขึ้นโดยอัตโนมัติ

อัปเดตฟิลด์ที่เฉพาะเจาะจง

หากต้องการเขียนไปยังโหนดย่อยที่เฉพาะเจาะจงของโหนดพร้อมกันโดยไม่เขียนทับโหนดย่อยอื่นๆ ให้ใช้เมธอด 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&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;
  }
}

หากต้องการสร้าง 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&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);
}

ตัวอย่างนี้ใช้ PushChild() เพื่อสร้างรายการในโหนดที่มีรายการสำหรับผู้ใช้ทั้งหมดที่ /scores/$key และเรียกข้อมูลคีย์พร้อมกันด้วย key() จากนั้นจะใช้คีย์เพื่อสร้างรายการที่ 2 ในคะแนนของผู้ใช้ที่ /user-scores/$userid/$key ได้

การใช้เส้นทางเหล่านี้จะช่วยให้คุณอัปเดตหลายตำแหน่งใน โครงสร้าง JSON พร้อมกันได้ด้วยการเรียกใช้ UpdateChildren() เพียงครั้งเดียว เช่น วิธีที่ ตัวอย่างนี้สร้างรายการใหม่ในทั้ง 2 ตำแหน่ง การอัปเดตพร้อมกันที่ทำในลักษณะนี้จะมีความสมบูรณ์ของทั้งฟีด: ไม่ว่าการอัปเดตทั้งหมดจะสำเร็จหรือล้มเหลว

ลบข้อมูล

วิธีที่ง่ายที่สุดในการลบข้อมูลคือการเรียกใช้ RemoveValue() ในการอ้างอิงถึง ตำแหน่งของข้อมูลนั้น

นอกจากนี้ คุณยังลบได้โดยระบุ null Variant เป็นค่าสําหรับการเขียนอีกรายการ เช่น SetValue() หรือ UpdateChildren() คุณสามารถใช้เทคนิคนี้กับ UpdateChildren() เพื่อลบข้อมูลของบุตรหลานหลายคนในการเรียก API ครั้งเดียวได้

รู้ว่าข้อมูลของคุณได้รับการคอมมิตเมื่อใด

หากต้องการทราบว่าข้อมูลได้รับการคอมมิตไปยังเซิร์ฟเวอร์ Firebase Realtime Database เมื่อใด ให้ตรวจสอบผลลัพธ์ Future เพื่อดูว่าสำเร็จหรือไม่

บันทึกข้อมูลเป็นธุรกรรม

เมื่อทำงานกับข้อมูลที่อาจเสียหายจากการแก้ไขพร้อมกัน เช่น ตัวนับแบบเพิ่ม คุณสามารถใช้การดำเนินการธุรกรรมได้ คุณกำหนดDoTransactionฟังก์ชันให้กับการดำเนินการนี้ ฟังก์ชันอัปเดตนี้ใช้สถานะปัจจุบันของข้อมูลเป็นอาร์กิวเมนต์และแสดงผลสถานะใหม่ที่ต้องการ ที่คุณต้องการเขียน หากไคลเอ็นต์อื่นเขียนไปยังตำแหน่งก่อนที่ระบบจะเขียนค่าใหม่ของคุณสำเร็จ ระบบจะเรียกฟังก์ชันอัปเดตอีกครั้งด้วยค่าปัจจุบันใหม่ และจะลองเขียนอีกครั้ง

เช่น ในเกม คุณอาจอนุญาตให้ผู้ใช้อัปเดตลีดเดอร์บอร์ดด้วยคะแนนสูงสุด 5 อันดับได้โดยทำดังนี้

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;
  });
}

การใช้ธุรกรรมจะช่วยป้องกันไม่ให้ลีดเดอร์บอร์ดไม่ถูกต้องในกรณีที่ผู้ใช้หลายคนบันทึกคะแนนพร้อมกันหรือไคลเอ็นต์มีข้อมูลที่ล้าสมัย หากธุรกรรมถูกปฏิเสธ เซิร์ฟเวอร์จะแสดงค่าปัจจุบันต่อไคลเอ็นต์ ซึ่งจะเรียกใช้ธุรกรรมอีกครั้งด้วยค่าที่อัปเดต ระบบจะทำซ้ำจนกว่าจะยอมรับธุรกรรมหรือมีการพยายามหลายครั้งเกินไป

เขียนข้อมูลแบบออฟไลน์

หากไคลเอ็นต์ขาดการเชื่อมต่อเครือข่าย แอปของคุณจะยังคงทำงานได้อย่างถูกต้อง

ไคลเอ็นต์ทุกรายที่เชื่อมต่อกับฐานข้อมูล Firebase จะรักษาเวอร์ชันภายในของตนเอง สำหรับข้อมูลที่ใช้งานอยู่ เมื่อมีการเขียนข้อมูล ระบบจะเขียนลงในเวอร์ชันในเครื่องนี้ก่อน จากนั้นไคลเอ็นต์ Firebase จะซิงโครไนซ์ข้อมูลดังกล่าวกับเซิร์ฟเวอร์ฐานข้อมูลระยะไกล และกับไคลเอ็นต์อื่นๆ โดยพยายามอย่างเต็มที่

ด้วยเหตุนี้ การเขียนทั้งหมดไปยังฐานข้อมูลจึงทําให้เกิดเหตุการณ์ในเครื่องทันที ก่อนที่จะมีการเขียนข้อมูลใดๆ ไปยังเซิร์ฟเวอร์ ซึ่งหมายความว่าแอปจะยังคงตอบสนองได้ไม่ว่าเครือข่ายจะมีเวลาในการตอบสนองหรือการเชื่อมต่อเป็นอย่างไร

เมื่อมีการเชื่อมต่ออีกครั้ง แอปจะได้รับชุดเหตุการณ์ที่เหมาะสมเพื่อให้ไคลเอ็นต์ซิงค์กับสถานะเซิร์ฟเวอร์ปัจจุบันโดยไม่ต้องเขียนโค้ดที่กำหนดเอง

ขั้นตอนถัดไป