Firebase Summit에서 발표된 모든 내용을 살펴보고 Firebase로 앱을 빠르게 개발하고 안심하고 앱을 실행하는 방법을 알아보세요. 자세히 알아보기

C++용 Firebase 실시간 데이터베이스로 데이터 저장

컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요.

시작하다

아직 앱을 설정하고 데이터베이스에 액세스하지 않은 경우 Get Started 가이드를 먼저 참조하세요.

DatabaseReference 가져오기

데이터베이스에 데이터를 쓰려면 DatabaseReference 인스턴스가 필요합니다.

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

데이터 저장

Firebase 실시간 데이터베이스에 데이터를 쓰는 방법에는 네 가지가 있습니다.

방법 일반적인 용도
SetValue() users/<user-id>/<username> 과 같은 정의된 경로에 데이터를 쓰거나 바꿉니다.
PushChild() 데이터 목록에 추가합니다. Push() 를 호출할 때마다 Firebase는 user-scores/<user-id>/<unique-score-id> 와 같은 고유 식별자로도 사용할 수 있는 고유 키를 생성합니다.
UpdateChildren() 모든 데이터를 바꾸지 않고 정의된 경로에 대한 일부 키를 업데이트합니다.
RunTransaction() 동시 업데이트로 인해 손상될 수 있는 복잡한 데이터를 업데이트합니다.

참조에서 데이터 쓰기, 업데이트 또는 삭제

기본 쓰기 작업

기본 쓰기 작업의 경우 SetValue() 를 사용하여 지정된 참조에 데이터를 저장하고 해당 경로에 있는 기존 데이터를 바꿀 수 있습니다. 이 방법을 사용하여 다음을 지원하는 Variant 유형을 통해 JSON에서 허용하는 유형을 전달할 수 있습니다.

  • Null(데이터 삭제)
  • 정수(64비트)
  • 배정밀도 부동 소수점 숫자
  • 부울
  • 문자열
  • 변이의 벡터
  • 문자열을 Variant로 매핑

이 방법으로 SetValue() 를 사용하면 자식 노드를 포함하여 지정된 위치의 데이터를 덮어씁니다. 그러나 전체 개체를 다시 작성하지 않고도 자식을 업데이트할 수 있습니다. 사용자가 프로필을 업데이트하도록 허용하려면 다음과 같이 사용자 이름을 업데이트할 수 있습니다.

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

데이터 목록에 추가

PushChild() 메서드를 사용하여 다중 사용자 응용 프로그램의 목록에 데이터를 추가합니다. PushChild() 메서드는 지정된 Firebase 참조에 새 자식이 추가될 때마다 고유 키를 생성합니다. 목록의 각 새 요소에 대해 이러한 자동 생성 키를 사용하여 여러 클라이언트가 쓰기 충돌 없이 동시에 동일한 위치에 자식을 추가할 수 있습니다. 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() 를 호출하는 것입니다.

SetValue() 또는 UpdateChildren() 과 같은 다른 쓰기 작업의 값으로 null Variant 를 지정하여 삭제할 수도 있습니다. UpdateChildren() 과 함께 이 기술을 사용하여 단일 API 호출에서 여러 자식을 삭제할 수 있습니다.

데이터가 커밋된 시점을 알 수 있습니다.

데이터가 Firebase 실시간 데이터베이스 서버에 커밋된 시점을 확인하려면 Future 결과에서 성공 여부를 확인하세요.

데이터를 트랜잭션으로 저장

증분 카운터와 같이 동시 수정으로 인해 손상될 수 있는 데이터로 작업할 때 트랜잭션 작업 을 사용할 수 있습니다. 이 작업에 DoTransaction 기능을 제공합니다. 이 업데이트 함수는 데이터의 현재 상태를 인수로 취하고 작성하려는 새로운 원하는 상태를 반환합니다. 새 값이 성공적으로 기록되기 전에 다른 클라이언트가 위치에 쓰는 경우 업데이트 함수가 새 현재 값으로 다시 호출되고 쓰기가 재시도됩니다.

예를 들어 게임에서 사용자가 최고 점수 5개로 순위표를 업데이트하도록 허용할 수 있습니다.

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 클라이언트는 해당 데이터를 "최선의" 방식으로 원격 데이터베이스 서버 및 다른 클라이언트와 동기화합니다.

결과적으로 데이터베이스에 대한 모든 쓰기는 데이터가 서버에 쓰기 전에 즉시 로컬 이벤트를 트리거합니다. 즉, 네트워크 대기 시간이나 연결에 관계없이 앱이 응답하는 상태를 유지합니다.

연결이 다시 설정되면 사용자 지정 코드를 작성할 필요 없이 클라이언트가 현재 서버 상태와 동기화할 수 있도록 앱이 적절한 이벤트 집합을 수신합니다.

다음 단계