Salvataggio dei dati

Questo documento illustra i quattro metodi per scrivere i dati nel database in tempo reale di Firebase: impostazione, aggiornamento, push e supporto delle transazioni.

Modi per salvare i dati

impostare Scrivi o sostituisci i dati in un percorso definito , come messages/users/<username>
aggiornare Aggiorna alcune chiavi per un percorso definito senza sostituire tutti i dati
spingere Aggiungi a un elenco di dati nel database. Ogni volta che inserisci un nuovo nodo in un elenco, il tuo database genera una chiave univoca, come messages/users/<unique-user-id>/<username>
transazione Utilizzare le transazioni quando si lavora con dati complessi che potrebbero essere danneggiati da aggiornamenti simultanei

Salvataggio dei dati

L'operazione di scrittura del database di base è un set che salva i nuovi dati nel riferimento del database specificato, sostituendo tutti i dati esistenti in quel percorso. Per comprendere il set, creeremo una semplice app di blogging. I dati per la tua app sono archiviati in questo riferimento di database:

Giava
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');
Pitone
# Import database module.
from firebase_admin import db

# Get a database reference to our blog.
ref = db.reference('server/saving-data/fireblog')
andare
// 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")

Iniziamo salvando alcuni dati utente. Conserveremo ogni utente con un nome utente univoco e memorizzeremo anche il nome completo e la data di nascita. Poiché ogni utente avrà un nome utente univoco, ha senso utilizzare qui il metodo set anziché il metodo push poiché hai già la chiave e non è necessario crearne una.

Innanzitutto, crea un riferimento al database per i tuoi dati utente. Quindi utilizzare set() / setValue() per salvare un oggetto utente nel database con il nome utente, il nome completo e la data di nascita dell'utente. Puoi passare imposta una stringa, un numero, un booleano, null , un array o qualsiasi oggetto JSON. Il passaggio di null rimuoverà i dati nella posizione specificata. In questo caso gli passerai un oggetto:

Giava
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'
  }
});
Pitone
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'
    }
})
andare

// 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 un oggetto JSON viene salvato nel database, le proprietà dell'oggetto vengono mappate automaticamente alle posizioni figlio del database in modo nidificato. Ora se vai all'URL https://docs-examples.firebaseio.com/server/saving-data/fireblog/users/alanisawesome/full_name , vedremo il valore "Alan Turing". Puoi anche salvare i dati direttamente in una posizione secondaria:

Giava
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'
});
Pitone
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'
})
andare
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)
}

I due esempi precedenti - scrivere entrambi i valori contemporaneamente come un oggetto e scriverli separatamente nelle posizioni figlio - risulteranno nel salvataggio degli stessi dati nel database:

{
  "users": {
    "alanisawesome": {
      "date_of_birth": "June 23, 1912",
      "full_name": "Alan Turing"
    },
    "gracehop": {
      "date_of_birth": "December 9, 1906",
      "full_name": "Grace Hopper"
    }
  }
}

Il primo esempio attiverà un solo evento sui client che stanno guardando i dati, mentre il secondo esempio ne attiverà due. È importante notare che se i dati esistessero già in usersRef , il primo approccio li sovrascriverebbe, ma il secondo metodo modificherebbe solo il valore di ogni nodo figlio separato lasciando invariati gli altri figli di usersRef .

Aggiornamento dei dati salvati

Se si desidera scrivere contemporaneamente su più elementi figlio di una posizione di database senza sovrascrivere altri nodi figlio, è possibile utilizzare il metodo di aggiornamento come mostrato di seguito:

Giava
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'
});
Pitone
hopper_ref = users_ref.child('gracehop')
hopper_ref.update({
    'nickname': 'Amazing Grace'
})
andare
hopperRef := usersRef.Child("gracehop")
if err := hopperRef.Update(ctx, map[string]interface{}{
	"nickname": "Amazing Grace",
}); err != nil {
	log.Fatalln("Error updating child:", err)
}

Questo aggiornerà i dati di Grace per includere il suo soprannome. Se avessi usato set here invece di update, avresti cancellato sia full_name che date_of_birth dal tuo hopperRef .

Il database Firebase Realtime supporta anche gli aggiornamenti multi-percorso. Ciò significa che l'aggiornamento ora può aggiornare i valori in più posizioni nel database contemporaneamente, una potente funzionalità che consente di denormalizzare i dati . Usando gli aggiornamenti multi-percorso, puoi aggiungere soprannomi a Grace e Alan contemporaneamente:

Giava
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'
});
Pitone
users_ref.update({
    'alanisawesome/nickname': 'Alan The Machine',
    'gracehop/nickname': 'Amazing Grace'
})
andare
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)
}

Dopo questo aggiornamento, sia Alan che Grace hanno aggiunto i loro soprannomi:

{
  "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"
    }
  }
}

Si noti che il tentativo di aggiornare gli oggetti scrivendo oggetti con i percorsi inclusi risulterà in un comportamento diverso. Diamo un'occhiata a cosa succede se invece provi ad aggiornare Grace e Alan in questo modo:

Giava
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'
  }
});
Pitone
users_ref.update({
    'alanisawesome': {
        'nickname': 'Alan The Machine'
    },
    'gracehop': {
        'nickname': 'Amazing Grace'
    }
})
andare
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)
}

Ciò si traduce in un comportamento diverso, vale a dire sovrascrivendo l'intero nodo /users :

{
  "users": {
    "alanisawesome": {
      "nickname": "Alan The Machine"
    },
    "gracehop": {
      "nickname": "Amazing Grace"
    }
  }
}

Aggiunta di una richiamata di completamento

In Node.js e Java Admin SDK, se desideri sapere quando è stato eseguito il commit dei tuoi dati, puoi aggiungere un callback di completamento. Sia i metodi di impostazione che quelli di aggiornamento in questi SDK richiedono un callback di completamento facoltativo che viene chiamato quando la scrittura è stata salvata nel database. Se la chiamata non ha avuto esito positivo per qualche motivo, al callback viene passato un oggetto di errore che indica il motivo per cui si è verificato l'errore. In Python e Go Admin SDK, tutti i metodi di scrittura stanno bloccando. Ovvero, i metodi di scrittura non vengono restituiti finché le scritture non vengono salvate nel database.

Giava
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.');
  }
});

Salvataggio di elenchi di dati

Quando si creano elenchi di dati, è importante tenere presente la natura multiutente della maggior parte delle applicazioni e regolare di conseguenza la struttura dell'elenco. Espandendo l'esempio sopra, aggiungiamo post del blog alla tua app. Il tuo primo istinto potrebbe essere quello di utilizzare set per memorizzare i bambini con indici interi a incremento automatico, come il seguente:

// NOT RECOMMENDED - use push() instead!
{
  "posts": {
    "0": {
      "author": "gracehop",
      "title": "Announcing COBOL, a New Programming Language"
    },
    "1": {
      "author": "alanisawesome",
      "title": "The Turing Machine"
    }
  }
}

Se un utente aggiunge un nuovo post, verrà archiviato come /posts/2 . Funzionerebbe se un solo autore aggiungesse post, ma nella tua applicazione di blog collaborativo molti utenti potrebbero aggiungere post contemporaneamente. Se due autori scrivono a /posts/2 contemporaneamente, uno dei post verrebbe cancellato dall'altro.

Per risolvere questo problema, i client Firebase forniscono una funzione push() che genera una chiave univoca per ogni nuovo figlio . Utilizzando chiavi figlio univoche, diversi client possono aggiungere bambini nella stessa posizione contemporaneamente senza preoccuparsi di conflitti di scrittura.

Giava
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'
});
Pitone
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'
})
andare

// 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 chiave univoca si basa su un timestamp, quindi gli elementi dell'elenco verranno automaticamente ordinati in ordine cronologico. Poiché Firebase genera una chiave univoca per ogni post del blog, non si verificheranno conflitti di scrittura se più utenti aggiungono un post contemporaneamente. I dati del tuo database ora sono così:

{
  "posts": {
    "-JRHTHaIs-jNPLXOQivY": {
      "author": "gracehop",
      "title": "Announcing COBOL, a New Programming Language"
    },
    "-JRHTHaKuITFIhnj02kE": {
      "author": "alanisawesome",
      "title": "The Turing Machine"
    }
  }
}

In JavaScript, Python e Go, lo schema di chiamare push() e quindi chiamare immediatamente set() è così comune che Firebase SDK ti consente di combinarli passando i dati da impostare direttamente a push() come segue:

Giava
// 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'
});;
Pitone
# This is equivalent to the calls to push().set(...) above
posts_ref.push({
    'author': 'gracehop',
    'title': 'Announcing COBOL, a New Programming Language'
})
andare
if _, err := postsRef.Push(ctx, &Post{
	Author: "gracehop",
	Title:  "Announcing COBOL, a New Programming Language",
}); err != nil {
	log.Fatalln("Error pushing child node:", err)
}

Ottenere la chiave univoca generata da push()

La chiamata push() restituirà un riferimento al nuovo percorso di dati, che puoi utilizzare per ottenere la chiave o impostare i dati su di essa. Il codice seguente produrrà gli stessi dati dell'esempio precedente, ma ora avremo accesso alla chiave univoca che è stata generata:

Giava
// 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;
Pitone
# 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
andare
// 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

Come puoi vedere, puoi ottenere il valore della chiave univoca dal tuo riferimento push() .

Nella prossima sezione sul recupero dei dati , impareremo come leggere questi dati da un database Firebase.

Salvataggio dei dati transazionali

Quando si lavora con dati complessi che potrebbero essere danneggiati da modifiche simultanee, come i contatori incrementali, l'SDK fornisce un'operazione di transazione .

In Java e Node.js, si assegnano all'operazione di transazione due callback: una funzione di aggiornamento e una callback di completamento opzionale. In Python e Go, l'operazione di transazione si blocca e quindi accetta solo la funzione di aggiornamento.

La funzione di aggiornamento prende lo stato corrente dei dati come argomento e dovrebbe restituire il nuovo stato desiderato che si desidera scrivere. Ad esempio, se desideri aumentare il numero di voti positivi su uno specifico post del blog, dovresti scrivere una transazione come la seguente:

Giava
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;
});
Pitone
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')
andare
fn := func(t db.TransactionNode) (interface{}, error) {
	var currentValue int
	if err := t.Unmarshal(&currentValue); 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'esempio precedente verifica se il contatore è null o non è stato ancora incrementato, poiché le transazioni possono essere chiamate con null se non è stato scritto alcun valore predefinito.

Se il codice precedente fosse stato eseguito senza una funzione di transazione e due client avessero tentato di incrementarlo contemporaneamente, scriverebbero entrambi 1 come nuovo valore, risultando in un incremento invece di due.

Connettività di rete e scritture offline

I client Firebase Node.js e Java mantengono la propria versione interna di tutti i dati attivi. Quando i dati vengono scritti, vengono scritti prima in questa versione locale. Il client sincronizza quindi i dati con il database e con altri client in base al "miglior sforzo".

Di conseguenza, tutte le scritture nel database attiveranno immediatamente eventi locali, prima ancora che i dati siano stati scritti nel database. Ciò significa che quando scrivi un'applicazione utilizzando Firebase, la tua app rimarrà reattiva indipendentemente dalla latenza di rete o dalla connettività Internet.

Una volta ristabilita la connettività, riceveremo il set di eventi appropriato in modo che il client "raggiunga" lo stato corrente del server, senza dover scrivere alcun codice personalizzato.

Protezione dei tuoi dati

Il database Firebase Realtime ha un linguaggio di sicurezza che ti consente di definire quali utenti hanno accesso in lettura e scrittura a diversi nodi dei tuoi dati. Puoi leggere di più in Proteggi i tuoi dati .