Ce document présente les quatre méthodes d'écriture de données dans votre Firebase Realtime Database: set, update, push et prise en charge des transactions.
Économiser des données
set | Écrire ou remplacer des données dans un chemin d'accès défini, comme messages/users/<username> |
update | Mettre à jour certaines clés d'un chemin défini sans remplacer toutes les données |
transmettre | Ajouter à une liste de données dans la base de données. Chaque fois que vous ajoutez un nouveau nœud à une liste, votre base de données génère une clé unique, comme messages/users/<unique-user-id>/<username> . |
transaction | Utilisez des transactions lorsque vous travaillez avec des données complexes qui pourraient être corrompues par des mises à jour simultanées. |
Enregistrement des données
L'opération d'écriture de base de données de base est un ensemble qui enregistre de nouvelles données dans la référence de base de données spécifiée, en remplaçant toutes les données existantes à ce chemin d'accès. Pour comprendre le concept de set, nous allons créer une application de blog simple. Les données de votre application sont stockées à cette référence de base de données:
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')
Accéder
// 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")
Commençons par enregistrer des données utilisateur. Nous enregistrons chaque utilisateur par un nom d'utilisateur unique, ainsi que son nom complet et sa date de naissance. Étant donné que chaque utilisateur possède un nom d'utilisateur unique, il est judicieux d'utiliser la méthode set ici plutôt que la méthode push, car vous disposez déjà de la clé et n'avez pas besoin d'en créer une.
Commencez par créer une référence de base de données pour vos données utilisateur. Utilisez ensuite set()
/ setValue()
pour enregistrer un objet utilisateur dans la base de données avec son nom d'utilisateur, son nom complet et son anniversaire. Vous pouvez définir une chaîne, un nombre, une valeur booléenne, un null
, un tableau ou un objet JSON. Si vous transmettez null
, les données seront supprimées à l'emplacement spécifié. Dans ce cas, vous lui transmettrez un objet:
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' } })
Accéder
// 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) }
Lorsqu'un objet JSON est enregistré dans la base de données, les propriétés de l'objet sont automatiquement mappées de manière imbriquée sur les emplacements enfants de la base de données. Si vous accédez à l'URL https://docs-examples.firebaseio.com/server/saving-data/fireblog/users/alanisawesome/full_name, la valeur "Alan Turing" s'affiche. Vous pouvez également enregistrer des données directement dans un emplacement enfant:
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' })
Accéder
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) }
Les deux exemples ci-dessus (écriture des deux valeurs en même temps en tant qu'objet et écriture séparée dans les emplacements enfants) entraîneront l'enregistrement des mêmes données dans votre base de données:
{ "users": { "alanisawesome": { "date_of_birth": "June 23, 1912", "full_name": "Alan Turing" }, "gracehop": { "date_of_birth": "December 9, 1906", "full_name": "Grace Hopper" } } }
Le premier exemple ne déclenche qu'un seul événement sur les clients qui surveillent les données, tandis que le second en déclenche deux. Il est important de noter que si les données existent déjà à usersRef
, la première méthode les écraserait, mais la seconde méthode ne modifierait que la valeur de chaque nœud enfant distinct tout en laissant les autres enfants de usersRef
inchangés.
Mise à jour des données enregistrées
Si vous souhaitez écrire simultanément dans plusieurs enfants d'un emplacement de base de données sans écraser d'autres nœuds enfants, vous pouvez utiliser la méthode de mise à jour, comme indiqué ci-dessous:
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' })
Accéder
hopperRef := usersRef.Child("gracehop") if err := hopperRef.Update(ctx, map[string]interface{}{ "nickname": "Amazing Grace", }); err != nil { log.Fatalln("Error updating child:", err) }
Les données de Grace seront mises à jour pour inclure son surnom. Si vous aviez utilisé la valeur définie ici au lieu de la mise à jour, full_name
et date_of_birth
auraient été supprimés de votre hopperRef
.
Firebase Realtime Database est également compatible avec les mises à jour multi-chemins. Cela signifie que la mise à jour peut désormais mettre à jour des valeurs à plusieurs endroits de votre base de données en même temps. Il s'agit d'une fonctionnalité puissante qui vous permet de dénormaliser vos données. Grâce aux mises à jour multi-chemins, vous pouvez ajouter des pseudos à Grace et à Alan en même temps:
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' })
Accéder
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) }
Après cette mise à jour, les surnoms d'Alan et de Grace ont été ajoutés:
{ "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" } } }
Notez que le comportement sera différent si vous essayez de mettre à jour des objets en écrivant des objets avec les chemins inclus. Voyons ce qui se passe si vous essayez de mettre à jour Grace et Alan comme suit:
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' } })
Accéder
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) }
Cela entraîne un comportement différent, à savoir l'écrasement de l'intégralité du nœud /users
:
{ "users": { "alanisawesome": { "nickname": "Alan The Machine" }, "gracehop": { "nickname": "Amazing Grace" } } }
Ajouter un rappel de fin de session
Dans les SDK d'administration Node.js et Java, si vous souhaitez savoir quand vos données ont été validées, vous pouvez ajouter un rappel de fin. Les méthodes set et update de ces SDK acceptent un rappel de finalisation facultatif qui est appelé lorsque l'écriture a été validée dans la base de données. Si l'appel a échoué pour une raison quelconque, un objet d'erreur indiquant pourquoi l'échec s'est produit est transmis au rappel. Dans les SDK Admin Python et Go, toutes les méthodes d'écriture sont bloquantes. Autrement dit, les méthodes d'écriture ne renvoient aucun résultat tant que les écritures n'ont pas été validées dans la base de données.
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.'); } });
Enregistrer des listes de données
Lorsque vous créez des listes de données, il est important de garder à l'esprit la nature multi-utilisateur de la plupart des applications et d'ajuster la structure de votre liste en conséquence. Pour développer l'exemple ci-dessus, ajoutons des articles de blog à votre application. Votre premier réflexe pourrait être d'utiliser un ensemble pour stocker des enfants avec des index entiers incrémentiels automatiques, comme suit:
// NOT RECOMMENDED - use push() instead! { "posts": { "0": { "author": "gracehop", "title": "Announcing COBOL, a New Programming Language" }, "1": { "author": "alanisawesome", "title": "The Turing Machine" } } }
Si un utilisateur ajoute un post, il sera stocké en tant que /posts/2
. Cela fonctionnerait si un seul auteur ajoutait des posts, mais dans votre application de blog collaboratif, de nombreux utilisateurs peuvent ajouter des posts en même temps. Si deux auteurs écrivent simultanément dans /posts/2
, l'un des messages est supprimé par l'autre.
Pour résoudre ce problème, les clients Firebase fournissent une fonction push()
qui génère une clé unique pour chaque nouvel enfant. En utilisant des clés enfants uniques, plusieurs clients peuvent ajouter des enfants au même emplacement en même temps sans avoir à se soucier des conflits d'écriture.
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' })
Accéder
// 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) }
La clé unique est basée sur un code temporel. Les éléments de la liste sont donc automatiquement triés par ordre chronologique. Étant donné que Firebase génère une clé unique pour chaque article de blog, aucun conflit d'écriture ne se produit si plusieurs utilisateurs ajoutent un article en même temps. Vos données de base de données se présentent désormais comme suit:
{ "posts": { "-JRHTHaIs-jNPLXOQivY": { "author": "gracehop", "title": "Announcing COBOL, a New Programming Language" }, "-JRHTHaKuITFIhnj02kE": { "author": "alanisawesome", "title": "The Turing Machine" } } }
En JavaScript, Python et Go, le modèle d'appel de push()
, puis d'appel immédiat de set()
est si courant que le SDK Firebase vous permet de les combiner en transmettant les données à définir directement à push()
comme suit:
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' })
Accéder
if _, err := postsRef.Push(ctx, &Post{ Author: "gracehop", Title: "Announcing COBOL, a New Programming Language", }); err != nil { log.Fatalln("Error pushing child node:", err) }
Obtenir la clé unique générée par push()
L'appel de push()
renvoie une référence au nouveau chemin de données, que vous pouvez utiliser pour obtenir la clé ou y définir des données. Le code suivant génère les mêmes données que l'exemple ci-dessus, mais nous avons désormais accès à la clé unique générée:
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
Accéder
// 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
Comme vous pouvez le voir, vous pouvez obtenir la valeur de la clé unique à partir de votre référence push()
.
Dans la section suivante intitulée Récupérer des données, vous allez apprendre à lire ces données à partir d'une base de données Firebase.
Enregistrer des données transactionnelles
Lorsque vous travaillez avec des données complexes qui peuvent être corrompues par des modifications simultanées, telles que des compteurs incrémentaux, le SDK fournit une opération de transaction.
En Java et en Node.js, vous fournissez à l'opération de transaction deux rappels: une fonction de mise à jour et un rappel de fin facultatif. Dans Python et Go, l'opération de transaction est bloquante et n'accepte donc que la fonction de mise à jour.
La fonction de mise à jour utilise l'état actuel des données comme argument et doit renvoyer le nouvel état souhaité que vous souhaitez écrire. Par exemple, si vous souhaitez incrémenter le nombre de votes positifs sur un article de blog spécifique, vous devez écrire une transaction comme suit:
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')
Accéder
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) }
L'exemple ci-dessus vérifie si le compteur est null
ou s'il n'a pas encore été incrémenté, car les transactions peuvent être appelées avec null
si aucune valeur par défaut n'a été écrite.
Si le code ci-dessus avait été exécuté sans fonction de transaction et que deux clients avaient tenté de l'incrémenter simultanément, ils auraient tous deux écrit 1
comme nouvelle valeur, ce qui aurait entraîné un incrément au lieu de deux.
Connectivité réseau et écritures hors connexion
Les clients Firebase Node.js et Java gèrent leur propre version interne de toutes les données actives. Lorsque des données sont écrites, elles le sont d'abord dans cette version locale. Le client synchronise ensuite ces données avec la base de données et avec d'autres clients dans la mesure du possible.
Par conséquent, toutes les écritures dans la base de données déclenchent immédiatement des événements locaux, avant même que des données aient été écrites dans la base de données. Cela signifie que lorsque vous écrivez une application à l'aide de Firebase, elle reste réactive quelle que soit la latence du réseau ou la connexion Internet.
Une fois la connectivité rétablie, nous recevrons l'ensemble d'événements approprié afin que le client "rattrape" l'état actuel du serveur, sans avoir à écrire de code personnalisé.
Sécuriser vos données
Firebase Realtime Database dispose d'un langage de sécurité qui vous permet de définir les utilisateurs disposant d'un accès en lecture et en écriture aux différents nœuds de vos données. Pour en savoir plus, consultez Sécuriser vos données.