Guardar datos

Este documento cubre los cuatro métodos para escribir datos en su Firebase Realtime Database: configuración, actualización, envío y soporte de transacciones.

Formas de guardar datos

colocar Escriba o reemplace datos en una ruta definida , como messages/users/<username>
actualizar Actualice algunas de las claves para una ruta definida sin reemplazar todos los datos
empujar Agregar a una lista de datos en la base de datos. Cada vez que inserta un nuevo nodo en una lista, su base de datos genera una clave única, como messages/users/<unique-user-id>/<username>
transacción Utilice transacciones cuando trabaje con datos complejos que podrían dañarse con actualizaciones simultáneas

Guardar datos

La operación básica de escritura de la base de datos es un conjunto que guarda datos nuevos en la referencia de la base de datos especificada, reemplazando cualquier dato existente en esa ruta. Para comprender Set, crearemos una aplicación de blogs sencilla. Los datos de su aplicación se almacenan en esta referencia de base de datos:

Java
final FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference ref = database.getReference("server/saving-data/fireblog");
Nodo.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');
Pitón
# Import database module.
from firebase_admin import db

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

Comencemos guardando algunos datos del usuario. Almacenaremos a cada usuario con un nombre de usuario único y también almacenaremos su nombre completo y fecha de nacimiento. Dado que cada usuario tendrá un nombre de usuario único, tiene sentido usar el método set aquí en lugar del método push, ya que ya tiene la clave y no necesita crear una.

Primero, cree una referencia de base de datos a sus datos de usuario. Luego use set() / setValue() para guardar un objeto de usuario en la base de datos con el nombre de usuario, el nombre completo y la fecha de nacimiento del usuario. Puede pasar set una cadena, número, booleano, null , matriz o cualquier objeto JSON. Pasar null eliminará los datos en la ubicación especificada. En este caso le pasarás un 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);
Nodo.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'
  }
});
Pitón
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'
    }
})
Ir

// 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)
}

Cuando se guarda un objeto JSON en la base de datos, las propiedades del objeto se asignan automáticamente a las ubicaciones secundarias de la base de datos de forma anidada. Ahora, si navega a la URL https://docs-examples.firebaseio.com/server/served-data/fireblog/users/alanisawesome/full_name , veremos el valor "Alan Turing". También puedes guardar datos directamente en la ubicación de un niño:

Java
usersRef.child("alanisawesome").setValueAsync(new User("June 23, 1912", "Alan Turing"));
usersRef.child("gracehop").setValueAsync(new User("December 9, 1906", "Grace Hopper"));
Nodo.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'
});
Pitón
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'
})
Ir
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)
}

Los dos ejemplos anteriores (escribir ambos valores al mismo tiempo como un objeto y escribirlos por separado en ubicaciones secundarias) darán como resultado que se guarden los mismos datos en su base de datos:

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

El primer ejemplo solo activará un evento en los clientes que estén observando los datos, mientras que el segundo ejemplo activará dos. Es importante tener en cuenta que si los datos ya existían en usersRef , el primer enfoque los sobrescribiría, pero el segundo método solo modificaría el valor de cada nodo secundario separado y dejaría a los demás hijos de usersRef sin cambios.

Actualización de datos guardados

Si desea escribir en varios nodos secundarios de una ubicación de base de datos al mismo tiempo sin sobrescribir otros nodos secundarios, puede utilizar el método de actualización como se muestra a continuación:

Java
DatabaseReference hopperRef = usersRef.child("gracehop");
Map<String, Object> hopperUpdates = new HashMap<>();
hopperUpdates.put("nickname", "Amazing Grace");

hopperRef.updateChildrenAsync(hopperUpdates);
Nodo.js
const usersRef = ref.child('users');
const hopperRef = usersRef.child('gracehop');
hopperRef.update({
  'nickname': 'Amazing Grace'
});
Pitón
hopper_ref = users_ref.child('gracehop')
hopper_ref.update({
    'nickname': 'Amazing Grace'
})
Ir
hopperRef := usersRef.Child("gracehop")
if err := hopperRef.Update(ctx, map[string]interface{}{
	"nickname": "Amazing Grace",
}); err != nil {
	log.Fatalln("Error updating child:", err)
}

Esto actualizará los datos de Grace para incluir su apodo. Si hubiera utilizado set aquí en lugar de actualizar, se habría eliminado tanto full_name como date_of_birth de su hopperRef .

Firebase Realtime Database también admite actualizaciones de múltiples rutas. Esto significa que la actualización ahora puede actualizar valores en múltiples ubicaciones de su base de datos al mismo tiempo, una característica poderosa que le ayuda a desnormalizar sus datos . Al utilizar actualizaciones de rutas múltiples, puede agregar apodos tanto a Grace como a Alan al mismo tiempo:

Java
Map<String, Object> userUpdates = new HashMap<>();
userUpdates.put("alanisawesome/nickname", "Alan The Machine");
userUpdates.put("gracehop/nickname", "Amazing Grace");

usersRef.updateChildrenAsync(userUpdates);
Nodo.js
const usersRef = ref.child('users');
usersRef.update({
  'alanisawesome/nickname': 'Alan The Machine',
  'gracehop/nickname': 'Amazing Grace'
});
Pitón
users_ref.update({
    'alanisawesome/nickname': 'Alan The Machine',
    'gracehop/nickname': 'Amazing Grace'
})
Ir
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)
}

Después de esta actualización, se agregaron sus apodos a Alan y Grace:

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

Tenga en cuenta que intentar actualizar objetos escribiéndolos con las rutas incluidas dará como resultado un comportamiento diferente. Echemos un vistazo a lo que sucede si intentas actualizar Grace y Alan de esta manera:

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);
Nodo.js
const usersRef = ref.child('users');
usersRef.update({
  'alanisawesome': {
    'nickname': 'Alan The Machine'
  },
  'gracehop': {
    'nickname': 'Amazing Grace'
  }
});
Pitón
users_ref.update({
    'alanisawesome': {
        'nickname': 'Alan The Machine'
    },
    'gracehop': {
        'nickname': 'Amazing Grace'
    }
})
Ir
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)
}

Esto da como resultado un comportamiento diferente, es decir, sobrescribir todo el nodo /users :

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

Agregar una devolución de llamada de finalización

En los SDK de Node.js y Java Admin, si desea saber cuándo se han confirmado sus datos, puede agregar una devolución de llamada de finalización. Tanto los métodos set como update en estos SDK requieren una devolución de llamada de finalización opcional que se llama cuando la escritura se ha confirmado en la base de datos. Si la llamada no tuvo éxito por algún motivo, a la devolución de llamada se le pasa un objeto de error que indica por qué ocurrió la falla. En los SDK de Python y Go Admin, todos los métodos de escritura están bloqueados. Es decir, los métodos de escritura no regresan hasta que las escrituras se confirman en la base de datos.

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.");
    }
  }
});
Nodo.js
dataRef.set('I\'m writing data', (error) => {
  if (error) {
    console.log('Data could not be saved.' + error);
  } else {
    console.log('Data saved successfully.');
  }
});

Guardar listas de datos

Al crear listas de datos, es importante tener en cuenta la naturaleza multiusuario de la mayoría de las aplicaciones y ajustar la estructura de la lista en consecuencia. Ampliando el ejemplo anterior, agreguemos publicaciones de blog a su aplicación. Su primer instinto podría ser utilizar set para almacenar elementos secundarios con índices enteros que se incrementan automáticamente, como el siguiente:

// 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 usuario agrega una nueva publicación, se almacenará como /posts/2 . Esto funcionaría si solo un autor agregara publicaciones, pero en su aplicación de blogs colaborativos muchos usuarios pueden agregar publicaciones al mismo tiempo. Si dos autores escriben en /posts/2 simultáneamente, la otra eliminará una de las publicaciones.

Para resolver esto, los clientes de Firebase proporcionan una función push() que genera una clave única para cada nuevo hijo . Al utilizar claves secundarias únicas, varios clientes pueden agregar hijos a la misma ubicación al mismo tiempo sin preocuparse por conflictos de escritura.

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"));
Nodo.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'
});
Pitón
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'
})
Ir

// 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 clave única se basa en una marca de tiempo, por lo que los elementos de la lista se ordenarán automáticamente cronológicamente. Debido a que Firebase genera una clave única para cada publicación de blog, no se producirán conflictos de escritura si varios usuarios agregan una publicación al mismo tiempo. Los datos de su base de datos ahora se ven así:

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

En JavaScript, Python y Go, el patrón de llamar push() y luego llamar inmediatamente set() es tan común que el SDK de Firebase te permite combinarlos pasando los datos que se configurarán directamente a push() de la siguiente manera:

Java
// No Java equivalent
Nodo.js
// This is equivalent to the calls to push().set(...) above
postsRef.push({
  author: 'gracehop',
  title: 'Announcing COBOL, a New Programming Language'
});;
Pitón
# This is equivalent to the calls to push().set(...) above
posts_ref.push({
    'author': 'gracehop',
    'title': 'Announcing COBOL, a New Programming Language'
})
Ir
if _, err := postsRef.Push(ctx, &Post{
	Author: "gracehop",
	Title:  "Announcing COBOL, a New Programming Language",
}); err != nil {
	log.Fatalln("Error pushing child node:", err)
}

Obtener la clave única generada por push()

Llamar a push() devolverá una referencia a la nueva ruta de datos, que puede usar para obtener la clave o configurar datos. El siguiente código generará los mismos datos que el ejemplo anterior, pero ahora tendremos acceso a la clave única que se generó:

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();
Nodo.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;
Pitón
# 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
Ir
// 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

Como puede ver, puede obtener el valor de la clave única de su referencia push() .

En la siguiente sección sobre Recuperación de datos , aprenderemos cómo leer estos datos desde una base de datos de Firebase.

Guardar datos transaccionales

Cuando se trabaja con datos complejos que podrían resultar dañados por modificaciones simultáneas, como contadores incrementales, el SDK proporciona una operación de transacción .

En Java y Node.js, le da a la operación de transacción dos devoluciones de llamada: una función de actualización y una devolución de llamada de finalización opcional. En Python y Go, la operación de transacción está bloqueando y por lo tanto solo acepta la función de actualización.

La función de actualización toma el estado actual de los datos como argumento y debe devolver el nuevo estado deseado que desea escribir. Por ejemplo, si quisiera incrementar el número de votos positivos en una publicación de blog específica, escribiría una transacción como la siguiente:

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");
  }
});
Nodo.js
const upvotesRef = db.ref('server/saving-data/fireblog/posts/-JRHTHaIs-jNPLXOQivY/upvotes');
upvotesRef.transaction((current_value) => {
  return (current_value || 0) + 1;
});
Pitón
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')
Ir
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)
}

El ejemplo anterior verifica si el contador es null o aún no se ha incrementado, ya que las transacciones se pueden llamar con null si no se escribió ningún valor predeterminado.

Si el código anterior se hubiera ejecutado sin una función de transacción y dos clientes intentaran incrementarlo simultáneamente, ambos escribirían 1 como nuevo valor, lo que daría como resultado un incremento en lugar de dos.

Conectividad de red y escrituras sin conexión

Los clientes Firebase Node.js y Java mantienen su propia versión interna de cualquier dato activo. Cuando se escriben datos, primero se escriben en esta versión local. Luego, el cliente sincroniza esos datos con la base de datos y con otros clientes haciendo el mejor esfuerzo posible.

Como resultado, todas las escrituras en la base de datos desencadenarán eventos locales inmediatamente, incluso antes de que se hayan escrito datos en la base de datos. Esto significa que cuando escribes una aplicación usando Firebase, tu aplicación seguirá respondiendo independientemente de la latencia de la red o la conectividad a Internet.

Una vez que se restablezca la conectividad, recibiremos el conjunto apropiado de eventos para que el cliente "se ponga al día" con el estado actual del servidor, sin tener que escribir ningún código personalizado.

Proteger sus datos

Firebase Realtime Database tiene un lenguaje de seguridad que le permite definir qué usuarios tienen acceso de lectura y escritura a diferentes nodos de sus datos. Puede leer más al respecto en Proteja sus datos .