Rozpocznij
Jeśli nie masz jeszcze skonfigurowanej aplikacji i dostępu do bazy danych, najpierw zapoznaj się z poradnikiem Get Started
.
Pobieranie obiektu DatabaseReference
Aby zapisać dane w bazie danych, potrzebujesz instancji DatabaseReference
:
// Get the root reference location of the database. firebase::database::DatabaseReference dbref = database->GetReference();
Zapisywanie danych
Dane do pliku Firebase Realtime Database można zapisywać na 4 sposoby:
Metoda | Typowe zastosowania |
---|---|
SetValue() |
Zapisz lub zastąp dane w określonej ścieżce, np. users/<user-id>/<username> . |
PushChild() |
Dodaj do listy danych. Za każdym razem, gdy wywołujesz funkcję Push() , Firebase generuje unikalny klucz, którego można też używać jako unikalnego identyfikatora, np. user-scores/<user-id>/<unique-score-id> . |
UpdateChildren() |
zaktualizować niektóre klucze na zdefiniowanej ścieżce bez zastępowania wszystkich danych; |
RunTransaction() |
aktualizować złożone dane, które mogą zostać uszkodzone przez równoległe aktualizacje; |
Zapisywanie, aktualizowanie i usuwanie danych w odwołaniach
Podstawowe operacje zapisu
W przypadku podstawowych operacji zapisu możesz użyć polecenia SetValue()
, aby zapisać dane w określonym odwołaniu, zastępując w przypadku danej ścieżki wszelkie dotychczasowe dane. Za pomocą tej metody możesz przekazywać typy akceptowane przez JSON za pomocą typu Wariant, który obsługuje:
- null (powoduje usunięcie danych);
- Liczba całkowita (64-bitowa)
- liczby zmiennoprzecinkowe podwójnej precyzji,
- Wartości logiczne
- Strings
- Wektory wariantów
- Mapowanie ciągów znaków na warianty
Użycie SetValue()
w ten sposób powoduje zastąpienie danych w wybranej lokalizacji, w tym wszystkich węzłów podrzędnych. Mimo to można zaktualizować element podrzędny bez przepisywania całego obiektu. Jeśli chcesz zezwolić użytkownikom na aktualizowanie swoich profili, możesz zmienić nazwę użytkownika w ten sposób:
dbref.Child("users").Child(userId).Child("username").SetValue(name);
Dołączanie do listy danych
Użyj metody PushChild()
, aby dołączać dane do listy w aplikacjach, które mają wielu użytkowników.
Metoda PushChild()
generuje unikalny klucz za każdym razem, gdy do określonego odwołania Firebase dodawane jest nowe dziecko. Dzięki tym automatycznie generowanym kluczom dla każdego nowego elementu na liście kilka klientów może dodawać podelementy do tej samej lokalizacji w tym samym czasie bez konfliktów podczas zapisu. Unikalny klucz wygenerowany przez funkcję PushChild()
jest oparty na sygnaturze czasowej, więc elementy listy są automatycznie sortowane chronologicznie.
Możesz użyć odwołania do nowych danych zwróconych przez metodę PushChild()
, aby uzyskać wartość automatycznie wygenerowanego klucza wydawcy podrzędnego lub ustawić dane dla elementu podrzędnego.
Wywołanie funkcji GetKey()
w przypadku odwołania PushChild()
zwraca wartość klucza wygenerowanego automatycznie.
Aktualizowanie konkretnych pól
Aby jednocześnie zapisywać dane w określonych węzłach podrzędnych bez nadpisywania innych węzłów podrzędnych, użyj metody UpdateChildren()
.
Gdy wywołujesz UpdateChildren()
, możesz zaktualizować wartości podrzędne niższego poziomu, podając ścieżkę klucza. Jeśli dane są przechowywane w kilku lokalizacjach, aby zapewnić lepszą skalowalność, możesz zaktualizować wszystkie wystąpienia tych danych za pomocą rozgałęzienia danych. Na przykład gra może mieć klasę LeaderboardEntry
taką:
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; } }
Aby utworzyć LeaderboardEntry
i jednocześnie zaktualizować go w bieżącym strumieniu wyników oraz na liście wyników użytkownika, gra używa tego kodu:
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); }
W tym przykładzie użyto funkcji PushChild()
do utworzenia w węźle wpisu zawierającego wpisy dla wszystkich użytkowników w organizacji /scores/$key
oraz jednoczesnego pobierania klucza za pomocą key()
. Za pomocą tego klucza można utworzyć drugi wpis w wynikach użytkownika w /user-scores/$userid/$key
.
Dzięki tym ścieżkom możesz przeprowadzać jednoczesne aktualizacje wielu lokalizacji w drzewie JSON za pomocą jednego wywołania funkcji UpdateChildren()
. W tym przykładzie nowy wpis jest tworzony w obu lokalizacjach. Jednoczesne aktualizacje w ten sposób są atomowe: albo wszystkie się powiodą, albo wszystkie się nie powiodą.
Usuń dane
Najprostszym sposobem usunięcia danych jest wywołanie metody RemoveValue()
w odniesieniu do lokalizacji tych danych.
Możesz też usunąć element, podając jako wartość dla innej operacji zapisu (np. SetValue()
lub UpdateChildren()
) wartość null
Variant
. Możesz użyć tej techniki z UpdateChildren()
, aby usunąć wiele elementów podrzędnych w pojedynczym wywołaniu interfejsu API.
Dowiedz się, kiedy Twoje dane są zapisywane.
Aby dowiedzieć się, kiedy Twoje dane są zapisywane na serwerze Firebase Realtime Database, sprawdź wynik Przyszłość.
Zapisywanie danych jako transakcji
Podczas pracy z danymi, które mogą zostać uszkodzone przez równoległe modyfikacje, np. z licznikami przyrostowymi, możesz użyć operacji transakcji.
Ta operacja ma funkcję DoTransaction
. Ta funkcja aktualizacji przyjmuje jako argument bieżący stan danych i zwraca nowy pożądany stan, który chcesz zapisać. Jeśli inny klient zapisze dane w lokalizacji, zanim Twoja nowa wartość zostanie zapisana, funkcja aktualizacji zostanie wywołana ponownie z nową bieżącą wartością, a zapis jest ponawiany.
W grze możesz na przykład zezwolić użytkownikom na aktualizowanie tabeli wyników za pomocą 5 najlepszych wyników:
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; }); }
Użycie transakcji zapobiega nieprawidłowej tablicy wyników, jeśli w tym samym czasie wielu użytkowników rejestruje swoje wyniki lub klient ma nieaktualne dane. Jeśli transakcja zostanie odrzucona, serwer zwróci bieżącą wartość klientowi, który ponownie wykona transakcję z aktualną wartością. Ta procedura jest powtarzana, dopóki transakcja nie zostanie zaakceptowana lub nie zostanie podjęta zbyt duża liczba prób.
Zapisywanie danych offline
Jeśli klient utraci połączenie sieciowe, aplikacja będzie nadal działać prawidłowo.
Każdy klient połączony z bazą danych Firebase utrzymuje własną wersję wewnętrzną wszystkich aktywnych danych. Podczas zapisywania danych są one najpierw zapisywane w tej wersji lokalnej. Następnie klient Firebase synchronizuje te dane ze zdalnymi serwerami bazy danych i z innymi klientami według zasady „najlepszego wysiłku”.
W rezultacie wszystkie zapisy w bazie danych powodują natychmiastowe wywołanie zdarzeń lokalnych, zanim jakiekolwiek dane zostaną zapisane na serwerze. Oznacza to, że aplikacja pozostaje responsywna niezależnie od opóźnień w sieci lub połączenia.
Po ponownym nawiązaniu połączenia aplikacja otrzymuje odpowiedni zestaw zdarzeń, dzięki czemu klient synchronizuje się z bieżącym stanem serwera, bez konieczności pisania kodu niestandardowego.