Este documento abrange os quatro métodos para gravar dados em seu Firebase Realtime Database: set, update, push e suporte a transações.
Maneiras de salvar dados
set | Gravar ou substituir dados em um caminho definido, como messages/users/<username> |
update | Atualize algumas das chaves de um caminho definido sem substituir todos os dados. |
push | Adicione a uma lista de dados no banco de dados. Sempre que um novo nó é enviado para uma lista, seu banco de dados gera uma chave única, como messages/users/<unique-user-id>/<username> |
transaction | Use transações ao trabalhar com dados complexos que poderiam ser corrompidos por atualizações simultâneas. |
Como salvar dados
A operação básica de gravação no banco de dados é um "set" que salva novos dados na referência do banco de dados especificada, substituindo todos os dados existentes nesse caminho. Para entender o conjunto, desenvolveremos um aplicativo de blog simples. Os dados do seu aplicativo são armazenados nesta referência de banco de dados:
Java
final FirebaseDatabase database = FirebaseDatabase.getInstance(); DatabaseReference ref = database.getReference("server/saving-data/fireblog");
Node.js
// Import Admin SDK const { getDatabase } = require('firebase-admin/database'); // Get a database reference to our blog const db = getDatabase(); const ref = db.ref('server/saving-data/fireblog');
Python
# Import database module. from firebase_admin import db # Get a database reference to our blog. ref = db.reference('server/saving-data/fireblog')
Go
// Create a database client from App. client, err := app.Database(ctx) if err != nil { log.Fatalln("Error initializing database client:", err) } // Get a database reference to our blog. ref := client.NewRef("server/saving-data/fireblog")
Vamos começar salvando alguns dados de usuários. Armazenaremos cada usuário com base em um nome de usuário individual, bem como os nomes completos e datas de nascimento deles. Como cada usuário terá um nome de usuário individual, faz sentido usar o método "set" em vez do método "push" porque você já tem a chave e não precisa criar uma.
Primeiro, crie uma referência de banco de dados para seus dados de usuário. Em seguida, use set()
/ setValue()
para salvar um objeto de usuário no banco de dados com o nome de usuário, o nome completo e a data de nascimento do usuário. É possível transmitir uma string, número, booleano, null
, matriz ou qualquer objeto JSON. Transmitir null
removerá os dados no local especificado. Neste caso, você transferirá um objeto:
Java
public static class User { public String date_of_birth; public String full_name; public String nickname; public User(String dateOfBirth, String fullName) { // ... } public User(String dateOfBirth, String fullName, String nickname) { // ... } } DatabaseReference usersRef = ref.child("users"); Map<String, User> users = new HashMap<>(); users.put("alanisawesome", new User("June 23, 1912", "Alan Turing")); users.put("gracehop", new User("December 9, 1906", "Grace Hopper")); usersRef.setValueAsync(users);
Node.js
const usersRef = ref.child('users'); usersRef.set({ alanisawesome: { date_of_birth: 'June 23, 1912', full_name: 'Alan Turing' }, gracehop: { date_of_birth: 'December 9, 1906', full_name: 'Grace Hopper' } });
Python
users_ref = ref.child('users') users_ref.set({ 'alanisawesome': { 'date_of_birth': 'June 23, 1912', 'full_name': 'Alan Turing' }, 'gracehop': { 'date_of_birth': 'December 9, 1906', 'full_name': 'Grace Hopper' } })
Go
// User is a json-serializable type. type User struct { DateOfBirth string `json:"date_of_birth,omitempty"` FullName string `json:"full_name,omitempty"` Nickname string `json:"nickname,omitempty"` } usersRef := ref.Child("users") err := usersRef.Set(ctx, map[string]*User{ "alanisawesome": { DateOfBirth: "June 23, 1912", FullName: "Alan Turing", }, "gracehop": { DateOfBirth: "December 9, 1906", FullName: "Grace Hopper", }, }) if err != nil { log.Fatalln("Error setting value:", err) }
Quando um objeto JSON é salvo no banco de dados, as propriedades do objeto são mapeadas automaticamente para locais filhos do banco de dados de maneira aninhada. Agora, se você acessar o URL https://docs-examples.firebaseio.com/server/saving-data/fireblog/users/alanisawesome/full_name, verá o valor "Alan Turing". Você também pode salvar dados diretamente em um local filho:
Java
usersRef.child("alanisawesome").setValueAsync(new User("June 23, 1912", "Alan Turing")); usersRef.child("gracehop").setValueAsync(new User("December 9, 1906", "Grace Hopper"));
Node.js
const usersRef = ref.child('users'); usersRef.child('alanisawesome').set({ date_of_birth: 'June 23, 1912', full_name: 'Alan Turing' }); usersRef.child('gracehop').set({ date_of_birth: 'December 9, 1906', full_name: 'Grace Hopper' });
Python
users_ref.child('alanisawesome').set({ 'date_of_birth': 'June 23, 1912', 'full_name': 'Alan Turing' }) users_ref.child('gracehop').set({ 'date_of_birth': 'December 9, 1906', 'full_name': 'Grace Hopper' })
Go
if err := usersRef.Child("alanisawesome").Set(ctx, &User{ DateOfBirth: "June 23, 1912", FullName: "Alan Turing", }); err != nil { log.Fatalln("Error setting value:", err) } if err := usersRef.Child("gracehop").Set(ctx, &User{ DateOfBirth: "December 9, 1906", FullName: "Grace Hopper", }); err != nil { log.Fatalln("Error setting value:", err) }
Os dois exemplos acima (gravar ambos os valores ao mesmo tempo como um objeto e gravá-los separadamente em locais filhos) fazem com que os mesmos dados sejam salvos no banco de dados:
{ "users": { "alanisawesome": { "date_of_birth": "June 23, 1912", "full_name": "Alan Turing" }, "gracehop": { "date_of_birth": "December 9, 1906", "full_name": "Grace Hopper" } } }
O primeiro exemplo só acionará um evento em clientes que estejam acompanhando os dados, enquanto o segundo exemplo acionará dois eventos. É importante observar que, se os dados já existirem em usersRef
, a primeira abordagem substituirá esses dados, mas o segundo método só modificará o valor de cada nó filho individual, deixando os demais filhos de usersRef
inalterados.
Atualizar dados salvos
Se você quiser gravar em vários filhos de um local de banco de dados ao mesmo tempo sem substituir outros nós filhos, poderá usar o método "update", como mostrado a seguir:
Java
DatabaseReference hopperRef = usersRef.child("gracehop"); Map<String, Object> hopperUpdates = new HashMap<>(); hopperUpdates.put("nickname", "Amazing Grace"); hopperRef.updateChildrenAsync(hopperUpdates);
Node.js
const usersRef = ref.child('users'); const hopperRef = usersRef.child('gracehop'); hopperRef.update({ 'nickname': 'Amazing Grace' });
Python
hopper_ref = users_ref.child('gracehop') hopper_ref.update({ 'nickname': 'Amazing Grace' })
Go
hopperRef := usersRef.Child("gracehop") if err := hopperRef.Update(ctx, map[string]interface{}{ "nickname": "Amazing Grace", }); err != nil { log.Fatalln("Error updating child:", err) }
Os dados de Grace serão atualizados para incluir o apelido. Se "set" tivesse sido usado em vez de "update", ele teria excluído full_name
e date_of_birth
do seu hopperRef
.
O Firebase Realtime Database também oferece suporte a atualizações em vários caminhos. Isso significa que agora é possível atualizar simultaneamente valores em diversos locais do seu banco de dados, um recurso poderoso que permite a desnormalização dos dados. Com as atualizações de vários caminhos, você pode adicionar apelidos para Grace e Alan ao mesmo tempo:
Java
Map<String, Object> userUpdates = new HashMap<>(); userUpdates.put("alanisawesome/nickname", "Alan The Machine"); userUpdates.put("gracehop/nickname", "Amazing Grace"); usersRef.updateChildrenAsync(userUpdates);
Node.js
const usersRef = ref.child('users'); usersRef.update({ 'alanisawesome/nickname': 'Alan The Machine', 'gracehop/nickname': 'Amazing Grace' });
Python
users_ref.update({ 'alanisawesome/nickname': 'Alan The Machine', 'gracehop/nickname': 'Amazing Grace' })
Go
if err := usersRef.Update(ctx, map[string]interface{}{ "alanisawesome/nickname": "Alan The Machine", "gracehop/nickname": "Amazing Grace", }); err != nil { log.Fatalln("Error updating children:", err) }
Após essa atualização, os apelidos de Alan e Grace serão adicionados:
{ "users": { "alanisawesome": { "date_of_birth": "June 23, 1912", "full_name": "Alan Turing", "nickname": "Alan The Machine" }, "gracehop": { "date_of_birth": "December 9, 1906", "full_name": "Grace Hopper", "nickname": "Amazing Grace" } } }
Se fizer isso gravando os objetos com os caminhos incluídos, o comportamento será diferente. Vejamos o que acontece se tentarmos atualizar Grace e Alan desta forma:
Java
Map<String, Object> userNicknameUpdates = new HashMap<>(); userNicknameUpdates.put("alanisawesome", new User(null, null, "Alan The Machine")); userNicknameUpdates.put("gracehop", new User(null, null, "Amazing Grace")); usersRef.updateChildrenAsync(userNicknameUpdates);
Node.js
const usersRef = ref.child('users'); usersRef.update({ 'alanisawesome': { 'nickname': 'Alan The Machine' }, 'gracehop': { 'nickname': 'Amazing Grace' } });
Python
users_ref.update({ 'alanisawesome': { 'nickname': 'Alan The Machine' }, 'gracehop': { 'nickname': 'Amazing Grace' } })
Go
if err := usersRef.Update(ctx, map[string]interface{}{ "alanisawesome": &User{Nickname: "Alan The Machine"}, "gracehop": &User{Nickname: "Amazing Grace"}, }); err != nil { log.Fatalln("Error updating children:", err) }
Isso resulta em um comportamento diferente, ou seja, todo o nó /users
é substituído:
{ "users": { "alanisawesome": { "nickname": "Alan The Machine" }, "gracehop": { "nickname": "Amazing Grace" } } }
Como adicionar um callback de conclusão
Nos Admin SDKs para Node.js e Java, para saber quando seus dados foram confirmados, você pode adicionar um retorno de chamada de conclusão. Os dois métodos nesses SDKs recebem um retorno de chamada de conclusão opcional, que será chamado quando a escrita for confirmada no banco de dados. Se, por qualquer motivo, a chamada falhar, o retorno de chamada receberá um objeto de erro indicando o motivo da falha. No SDK Admin do Python e do Go, todos os métodos de gravação ficam bloqueados, ou seja, não retornam até que as gravações estejam confirmadas no banco de dados.
Java
DatabaseReference dataRef = ref.child("data"); dataRef.setValue("I'm writing data", new DatabaseReference.CompletionListener() { @Override public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) { if (databaseError != null) { System.out.println("Data could not be saved " + databaseError.getMessage()); } else { System.out.println("Data saved successfully."); } } });
Node.js
dataRef.set('I\'m writing data', (error) => { if (error) { console.log('Data could not be saved.' + error); } else { console.log('Data saved successfully.'); } });
Salvar listas de dados
Ao criar listas de dados, é importante lembrar a natureza multiusuário da maioria dos aplicativos e ajustar adequadamente a estrutura da lista. Ampliando o exemplo acima, vamos adicionar postagens do blog ao seu aplicativo. Seu primeiro instinto pode ser usar "set" para armazenar filhos com índices inteiros de incremento automático, como:
// NOT RECOMMENDED - use push() instead! { "posts": { "0": { "author": "gracehop", "title": "Announcing COBOL, a New Programming Language" }, "1": { "author": "alanisawesome", "title": "The Turing Machine" } } }
Se um usuário adicionar uma nova postagem, ela será armazenada como /posts/2
. Isso funciona para um único autor que adiciona postagens. No entanto, no aplicativo de blog colaborativo, vários usuários podem adicionar postagens ao mesmo tempo. Se dois autores gravarem em /posts/2
simultaneamente, uma das publicações será excluída pela outra.
Para resolver isso, os clientes do Firebase fornecem uma função push()
que gera uma chave única para cada novo filho. Ao usar chaves filhas exclusivas, vários clientes poderão adicionar filhos ao mesmo local simultaneamente sem criar conflitos de gravação.
Java
public static class Post { public String author; public String title; public Post(String author, String title) { // ... } } DatabaseReference postsRef = ref.child("posts"); DatabaseReference newPostRef = postsRef.push(); newPostRef.setValueAsync(new Post("gracehop", "Announcing COBOL, a New Programming Language")); // We can also chain the two calls together postsRef.push().setValueAsync(new Post("alanisawesome", "The Turing Machine"));
Node.js
const newPostRef = postsRef.push(); newPostRef.set({ author: 'gracehop', title: 'Announcing COBOL, a New Programming Language' }); // we can also chain the two calls together postsRef.push().set({ author: 'alanisawesome', title: 'The Turing Machine' });
Python
posts_ref = ref.child('posts') new_post_ref = posts_ref.push() new_post_ref.set({ 'author': 'gracehop', 'title': 'Announcing COBOL, a New Programming Language' }) # We can also chain the two calls together posts_ref.push().set({ 'author': 'alanisawesome', 'title': 'The Turing Machine' })
Go
// Post is a json-serializable type. type Post struct { Author string `json:"author,omitempty"` Title string `json:"title,omitempty"` } postsRef := ref.Child("posts") newPostRef, err := postsRef.Push(ctx, nil) if err != nil { log.Fatalln("Error pushing child node:", err) } if err := newPostRef.Set(ctx, &Post{ Author: "gracehop", Title: "Announcing COBOL, a New Programming Language", }); err != nil { log.Fatalln("Error setting value:", err) } // We can also chain the two calls together if _, err := postsRef.Push(ctx, &Post{ Author: "alanisawesome", Title: "The Turing Machine", }); err != nil { log.Fatalln("Error pushing child node:", err) }
A chave exclusiva é baseada em um carimbo de data/hora. Portanto, os itens da lista são organizados automaticamente em ordem cronológica. Como o Firebase gera uma chave exclusiva para cada postagem do blog, nenhum conflito de escrita ocorrerá se diversos usuários adicionarem uma postagem ao mesmo tempo. Agora os dados do banco de dados estão assim:
{ "posts": { "-JRHTHaIs-jNPLXOQivY": { "author": "gracehop", "title": "Announcing COBOL, a New Programming Language" }, "-JRHTHaKuITFIhnj02kE": { "author": "alanisawesome", "title": "The Turing Machine" } } }
Em JavaScript, Python e Go, o padrão de chamar push()
e, em seguida, chamar set()
imediatamente é tão comum que o SDK do Firebase permite combiná-los transmitindo os dados para serem configurados diretamente para push()
da seguinte maneira:
Java
// No Java equivalent
Node.js
// This is equivalent to the calls to push().set(...) above postsRef.push({ author: 'gracehop', title: 'Announcing COBOL, a New Programming Language' });;
Python
# This is equivalent to the calls to push().set(...) above posts_ref.push({ 'author': 'gracehop', 'title': 'Announcing COBOL, a New Programming Language' })
Go
if _, err := postsRef.Push(ctx, &Post{ Author: "gracehop", Title: "Announcing COBOL, a New Programming Language", }); err != nil { log.Fatalln("Error pushing child node:", err) }
Como receber a chave exclusiva gerada por push()
Chamar push()
retornará uma referência ao novo caminho de dados, que pode ser usado para receber a chave ou definir dados para ela. O código a seguir resultará nos mesmos dados do exemplo acima, mas agora teremos acesso à chave exclusiva que foi gerada:
Java
// Generate a reference to a new location and add some data using push() DatabaseReference pushedPostRef = postsRef.push(); // Get the unique ID generated by a push() String postId = pushedPostRef.getKey();
Node.js
// Generate a reference to a new location and add some data using push() const newPostRef = postsRef.push(); // Get the unique key generated by push() const postId = newPostRef.key;
Python
# Generate a reference to a new location and add some data using push() new_post_ref = posts_ref.push() # Get the unique key generated by push() post_id = new_post_ref.key
Go
// Generate a reference to a new location and add some data using Push() newPostRef, err := postsRef.Push(ctx, nil) if err != nil { log.Fatalln("Error pushing child node:", err) } // Get the unique key generated by Push() postID := newPostRef.Key
Observe que é possível receber o valor da chave única da sua referência push()
.
Na próxima seção, em Como recuperar dados, veremos como ler esses dados de um database do Firebase.
Como salvar dados de transação
Ao trabalhar com dados complexos que podem ser corrompidos por modificações simultâneas, como contadores incrementais, o SDK fornece uma operação de transação.
Em Java e Node.js, essa operação de transação aceita dois retornos de chamada: de função de atualização e de conclusão opcional. Em Python e em Go, a operação de transação é de bloqueio e, portanto, aceita apenas a função de atualização.
A função de atualização entende o estado atual dos dados como argumento e deve retornar o novo estado desejado que você quer escrever. Por exemplo, para incrementar o número de votos positivos em uma postagem do blog específica, você pode escrever uma transação da seguinte maneira:
Java
DatabaseReference upvotesRef = ref.child("server/saving-data/fireblog/posts/-JRHTHaIs-jNPLXOQivY/upvotes"); upvotesRef.runTransaction(new Transaction.Handler() { @Override public Transaction.Result doTransaction(MutableData mutableData) { Integer currentValue = mutableData.getValue(Integer.class); if (currentValue == null) { mutableData.setValue(1); } else { mutableData.setValue(currentValue + 1); } return Transaction.success(mutableData); } @Override public void onComplete( DatabaseError databaseError, boolean committed, DataSnapshot dataSnapshot) { System.out.println("Transaction completed"); } });
Node.js
const upvotesRef = db.ref('server/saving-data/fireblog/posts/-JRHTHaIs-jNPLXOQivY/upvotes'); upvotesRef.transaction((current_value) => { return (current_value || 0) + 1; });
Python
def increment_votes(current_value): return current_value + 1 if current_value else 1 upvotes_ref = db.reference('server/saving-data/fireblog/posts/-JRHTHaIs-jNPLXOQivY/upvotes') try: new_vote_count = upvotes_ref.transaction(increment_votes) print('Transaction completed') except db.TransactionAbortedError: print('Transaction failed to commit')
Go
fn := func(t db.TransactionNode) (interface{}, error) { var currentValue int if err := t.Unmarshal(¤tValue); err != nil { return nil, err } return currentValue + 1, nil } ref := client.NewRef("server/saving-data/fireblog/posts/-JRHTHaIs-jNPLXOQivY/upvotes") if err := ref.Transaction(ctx, fn); err != nil { log.Fatalln("Transaction failed to commit:", err) }
O exemplo acima verifica se o contador é null
ou ainda não foi incrementado, já que as transações podem ser chamadas com null
se nenhum valor padrão foi gravado.
Se o código acima tivesse sido executado sem uma função de transação e dois clientes tentassem incrementá-lo simultaneamente, ambos escreveriam 1
como o novo valor, resultando em um incremento em vez de dois.
Conectividade de rede e escritas off-line
Clientes Node.js e Java do Firebase mantêm a própria versão interna de quaisquer dados ativos. Os dados são gravados primeiro nessa versão local. Em seguida, o cliente sincroniza esses dados com o banco de dados e com outros clientes de acordo com seu “melhor esforço”.
Consequentemente, todas as gravações no banco de dados acionam eventos locais imediatamente, antes que qualquer dado seja gravado no banco de dados. Isso significa que, ao criar um aplicativo usando o Firebase, ele manterá sua capacidade de resposta, independentemente da latência da rede ou da conectividade com a Internet.
Quando a conectividade for restabelecida, receberemos o conjunto apropriado de eventos para que o cliente seja sincronizado com o estado atual do servidor, sem necessidade de criar código personalizado.
Proteger seus dados
O Firebase Realtime Database tem uma linguagem de segurança que permite definir quais usuários têm acesso de leitura e gravação a diferentes nós dos dados. Saiba mais sobre isso em Como proteger seus dados.